diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2fd686187d9f2..cdfe0fcfbceeb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -804,7 +804,12 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations /x-pack/test/functional/services/uptime @elastic/uptime /x-pack/test/api_integration/apis/uptime @elastic/uptime /x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime - +/x-pack/plugins/observability/public/components/shared/field_value_suggestions @elastic/uptime +/x-pack/plugins/observability/public/components/shared/core_web_vitals @elastic/uptime +/x-pack/plugins/observability/public/components/shared/load_when_in_view @elastic/uptime +/x-pack/plugins/observability/public/components/shared/filter_value_label @elastic/uptime +/x-pack/plugins/observability/public/utils/observability_data_views @elastic/uptime +/x-pack/plugins/observability/e2e @elastic/uptime # Client Side Monitoring / Uptime (lives in APM directories but owned by Uptime) /x-pack/plugins/apm/public/application/uxApp.tsx @elastic/uptime @@ -876,14 +881,15 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations /.buildkite/ @elastic/kibana-operations /kbn_pm/ @elastic/kibana-operations -# Quality Assurance +# Appex QA /src/dev/code_coverage @elastic/appex-qa -/vars/*Coverage.groovy @elastic/appex-qa /test/functional/services/common @elastic/appex-qa /test/functional/services/lib @elastic/appex-qa /test/functional/services/remote @elastic/appex-qa /test/visual_regression @elastic/appex-qa /x-pack/test/visual_regression @elastic/appex-qa +/x-pack/performance @elastic/appex-qa +/packages/kbn-test/src/functional_test_runner @elastic/appex-qa # Core /config/kibana.yml @elastic/kibana-core @@ -1158,12 +1164,15 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience /x-pack/plugins/security_solution/public/detection_engine/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions @elastic/security-defend-workflows +# Cloud Defend +/x-pack/plugins/cloud_defend/ @elastic/sec-cloudnative-integrations +/x-pack/plugins/security_solution/public/cloud_defend @elastic/sec-cloudnative-integrations + # Cloud Security Posture /x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture /x-pack/test/api_integration/apis/cloud_security_posture/ @elastic/kibana-cloud-security-posture /x-pack/test/cloud_security_posture_functional/ @elastic/kibana-cloud-security-posture - # Security Solution onboarding tour /x-pack/plugins/security_solution/public/common/components/guided_onboarding @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/guided_onboarding @elastic/security-threat-hunting-explore @@ -1197,8 +1206,6 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience # Changes to translation files should not ping code reviewers x-pack/plugins/translations/translations -x-pack/performance @elastic/appex-qa - #### ## These rules are always last so they take ultimate priority over everything else #### diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 0b8e633709e11..d078e0ada29b1 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-02-28 +date: 2023-03-06 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 923c752e5b8ee..8eef0ea02d8d2 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 7570552b45d10..a008f12920d5d 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 896d6335b1e48..02812b7ec0cb8 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 064ab9c52e28f..c2ee16a6018d0 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 0d3965826809e..facbdf4d471f8 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-02-28 +date: 2023-03-06 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 e742fdf8dfc18..e2aa581d21808 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-02-28 +date: 2023-03-06 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 32b1952cafdd8..08c47490cf2c1 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-02-28 +date: 2023-03-06 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 22ad30c92f35e..4072d9d282f59 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -1321,6 +1321,39 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "cases", + "id": "def-common.getApiTags", + "type": "Function", + "tags": [], + "label": "getApiTags", + "description": [], + "signature": [ + "(owner: \"cases\" | \"observability\" | \"securitySolution\") => { all: readonly [\"casesSuggestUserProfiles\", \"bulkGetUserProfiles\", string, string]; read: readonly [\"casesSuggestUserProfiles\", \"bulkGetUserProfiles\", string]; delete: readonly [string]; }" + ], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cases", + "id": "def-common.getApiTags.$1", + "type": "CompoundType", + "tags": [], + "label": "owner", + "description": [], + "signature": [ + "\"cases\" | \"observability\" | \"securitySolution\"" + ], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.getCasesFromAlertsUrl", @@ -1614,7 +1647,7 @@ "signature": [ "\"cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/application.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1863,7 +1896,7 @@ "signature": [ "\"/api/cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2168,7 +2201,7 @@ "signature": [ "\"create_cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2183,7 +2216,7 @@ "signature": [ "\"delete_cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2198,7 +2231,7 @@ "signature": [ "\"cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/owners.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2213,7 +2246,7 @@ "signature": [ "\"/internal/cases/_bulk_get\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2228,7 +2261,7 @@ "signature": [ "\"observability\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/owners.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2243,7 +2276,7 @@ "signature": [ "\"push_cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2258,7 +2291,7 @@ "signature": [ "\"read_cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2275,7 +2308,7 @@ "signature": [ "\"securitySolution\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/owners.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2305,7 +2338,7 @@ "signature": [ "\"update_cases\"" ], - "path": "x-pack/plugins/cases/common/constants.ts", + "path": "x-pack/plugins/cases/common/constants/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 1ed8c149e896c..6f2e2ae031ba1 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.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 | |-------------------|-----------|------------------------|-----------------| -| 92 | 0 | 75 | 28 | +| 94 | 0 | 77 | 28 | ## Client diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 9d36327064aec..7dc97d1b16161 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-02-28 +date: 2023-03-06 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 659b85d1d7b28..d5c07ef8e047c 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-02-28 +date: 2023-03-06 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 9f42c75ffb3e9..9d2655555c7fd 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-02-28 +date: 2023-03-06 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 3809b2b6a701f..511acdb35de73 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.devdocs.json b/api_docs/cloud_defend.devdocs.json index f155214541b73..40015d4edbbb1 100644 --- a/api_docs/cloud_defend.devdocs.json +++ b/api_docs/cloud_defend.devdocs.json @@ -2,10 +2,188 @@ "id": "cloudDefend", "client": { "classes": [], - "functions": [], - "interfaces": [], + "functions": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionLink", + "type": "Function", + "tags": [], + "label": "getSecuritySolutionLink", + "description": [ + "\nGets the cloud_defend link properties of a Cloud Defend page for navigation in the security solution." + ], + "signature": [ + "(cloudDefendPage: \"policies\") => CloudDefendLinkItem" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionLink.$1", + "type": "string", + "tags": [], + "label": "cloudDefendPage", + "description": [ + "the name of the cloud defend page." + ], + "signature": [ + "\"policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab", + "type": "Function", + "tags": [], + "label": "getSecuritySolutionNavTab", + "description": [ + "\nGets the link properties of a Cloud Defend page for navigation in the old security solution navigation." + ], + "signature": [ + "(cloudDefendPage: \"policies\", basePath: string) => CloudDefendNavTab" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab.$1", + "type": "string", + "tags": [], + "label": "cloudDefendPage", + "description": [ + "the name of the cloud defend page." + ], + "signature": [ + "\"policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab.$2", + "type": "string", + "tags": [], + "label": "basePath", + "description": [ + "the base path for links." + ], + "signature": [ + "string" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext", + "type": "Interface", + "tags": [], + "label": "CloudDefendSecuritySolutionContext", + "description": [], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext.getFiltersGlobalComponent", + "type": "Function", + "tags": [], + "label": "getFiltersGlobalComponent", + "description": [ + "Gets the `FiltersGlobal` component for embedding a filter bar in the security solution application." + ], + "signature": [ + "() => React.ComponentType<{ children: React.ReactNode; }>" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext.getSpyRouteComponent", + "type": "Function", + "tags": [], + "label": "getSpyRouteComponent", + "description": [ + "Gets the `SpyRoute` component for navigation highlighting and breadcrumbs." + ], + "signature": [ + "() => React.ComponentType<{ pageName: \"cloud_defend-policies\"; state?: Record | undefined; }>" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CLOUD_DEFEND_BASE_PATH", + "type": "string", + "tags": [], + "label": "CLOUD_DEFEND_BASE_PATH", + "description": [ + "The base path for all cloud defend pages." + ], + "signature": [ + "\"/cloud_defend\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendPageId", + "type": "Type", + "tags": [], + "label": "CloudDefendPageId", + "description": [ + "\nAll the IDs for the cloud defend pages.\nThis needs to match the cloud defend page entries in `SecurityPageName` in `x-pack/plugins/security_solution/common/constants.ts`." + ], + "signature": [ + "\"cloud_defend-policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [], "setup": { "parentPluginId": "cloudDefend", @@ -13,7 +191,9 @@ "type": "Interface", "tags": [], "label": "CloudDefendPluginSetup", - "description": [], + "description": [ + "\ncloud_defend plugin types" + ], "path": "x-pack/plugins/cloud_defend/public/types.ts", "deprecated": false, "trackAdoption": false, @@ -31,7 +211,28 @@ "path": "x-pack/plugins/cloud_defend/public/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendPluginStart.getCloudDefendRouter", + "type": "Function", + "tags": [], + "label": "getCloudDefendRouter", + "description": [ + "Gets the cloud defend router component for embedding in the security solution." + ], + "signature": [ + "() => React.ComponentType<", + "CloudDefendRouterProps", + ">" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], "lifecycle": "start", "initialIsOpen": true } @@ -42,7 +243,35 @@ "interfaces": [], "enums": [], "misc": [], - "objects": [] + "objects": [], + "setup": { + "parentPluginId": "cloudDefend", + "id": "def-server.CloudDefendPluginSetup", + "type": "Interface", + "tags": [], + "label": "CloudDefendPluginSetup", + "description": [], + "path": "x-pack/plugins/cloud_defend/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "cloudDefend", + "id": "def-server.CloudDefendPluginStart", + "type": "Interface", + "tags": [], + "label": "CloudDefendPluginStart", + "description": [], + "path": "x-pack/plugins/cloud_defend/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } }, "common": { "classes": [], diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index da868144c7618..2951195e7b749 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,12 +8,12 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; -Defend for Containers +Defend for containers (D4C) Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) for questions regarding this plugin. @@ -21,7 +21,7 @@ Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2 | 0 | 2 | 0 | +| 15 | 0 | 4 | 1 | ## Client @@ -31,3 +31,20 @@ Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/ ### Start +### Functions + + +### Interfaces + + +### Consts, variables and types + + +## Server + +### Setup + + +### Start + + diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 4bb90591915e0..cc5e173d42619 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-02-28 +date: 2023-03-06 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 284069b14370d..0ca5a20160fb1 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-02-28 +date: 2023-03-06 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 e8e668d7e0993..873971f7b64bc 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.devdocs.json b/api_docs/content_management.devdocs.json index 896f476aaffb2..0b70a55a41998 100644 --- a/api_docs/content_management.devdocs.json +++ b/api_docs/content_management.devdocs.json @@ -1,11 +1,1050 @@ { "id": "contentManagement", "client": { - "classes": [], - "functions": [], - "interfaces": [], + "classes": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient", + "type": "Class", + "tags": [], + "label": "ContentClient", + "description": [], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.queryClient", + "type": "Object", + "tags": [], + "label": "queryClient", + "description": [], + "signature": [ + "QueryClient" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.queryOptionBuilder", + "type": "Object", + "tags": [], + "label": "queryOptionBuilder", + "description": [], + "signature": [ + "{ get: = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => { queryKey: readonly [string, string]; queryFn: () => Promise; }; search: = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ", O = unknown>(input: I) => { queryKey: readonly [string, \"search\", unknown]; queryFn: () => Promise; }; }" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.Unnamed.$1", + "type": "Function", + "tags": [], + "label": "crudClientProvider", + "description": [], + "signature": [ + "(contentType: string) => ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.CrudClient", + "text": "CrudClient" + } + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get$", + "type": "Function", + "tags": [], + "label": "get$", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => ", + "Observable", + "<", + "QueryObserverResult", + ">" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get$.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.create.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.update.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.delete.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search$", + "type": "Function", + "tags": [], + "label": "search$", + "description": [], + "signature": [ + ", O = unknown>(input: I) => ", + "Observable", + "<", + "QueryObserverResult", + ">" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search$.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClientProvider", + "type": "Function", + "tags": [], + "label": "ContentClientProvider", + "description": [], + "signature": [ + "({ contentClient, children, }: React.PropsWithChildren<{ contentClient: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + }, + "; }>) => JSX.Element" + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClientProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n contentClient,\n children,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<{ contentClient: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + }, + "; }>" + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useContentClient", + "type": "Function", + "tags": [], + "label": "useContentClient", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + } + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useCreateContentMutation", + "type": "Function", + "tags": [], + "label": "useCreateContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useDeleteContentMutation", + "type": "Function", + "tags": [], + "label": "useDeleteContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery", + "type": "Function", + "tags": [], + "label": "useGetContentQuery", + "description": [ + "\n" + ], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I, queryOptions?: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined) => ", + "UseQueryResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [ + "- get content identifier like \"id\" and \"contentType\"" + ], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery.$2", + "type": "Object", + "tags": [], + "label": "queryOptions", + "description": [ + "- query options" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery", + "type": "Function", + "tags": [], + "label": "useSearchContentQuery", + "description": [ + "\n" + ], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ", O = unknown>(input: I, queryOptions?: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined) => ", + "UseQueryResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [ + "- get content identifier like \"id\" and \"contentType\"" + ], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery.$2", + "type": "Object", + "tags": [], + "label": "queryOptions", + "description": [ + "- query options" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useUpdateContentMutation", + "type": "Function", + "tags": [], + "label": "useUpdateContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient", + "type": "Interface", + "tags": [], + "label": "CrudClient", + "description": [], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.get.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.create.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.update.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.delete.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.search.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.QueryOptions", + "type": "Type", + "tags": [], + "label": "QueryOptions", + "description": [ + "\nExposed `useQuery` options" + ], + "signature": [ + "{ enabled?: boolean | undefined; }" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [], "start": { "parentPluginId": "contentManagement", @@ -26,7 +1065,13 @@ "label": "client", "description": [], "signature": [ - "ContentClient" + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + } ], "path": "src/plugins/content_management/public/types.ts", "deprecated": false, @@ -91,7 +1136,541 @@ "server": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage", + "type": "Interface", + "tags": [], + "label": "ContentStorage", + "description": [], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [ + "Get a single item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet", + "type": "Function", + "tags": [], + "label": "bulkGet", + "description": [ + "Get multiple items" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", ids: string[], options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$2", + "type": "Array", + "tags": [], + "label": "ids", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [ + "Create an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", data: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$2", + "type": "Uncategorized", + "tags": [], + "label": "data", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [ + "Update an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, data: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$3", + "type": "Uncategorized", + "tags": [], + "label": "data", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$4", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [ + "Delete an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [ + "Search items" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", query: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$2", + "type": "Uncategorized", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.StorageContext", + "type": "Interface", + "tags": [], + "label": "StorageContext", + "description": [ + "Context that is sent to all storage instance methods" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.StorageContext.requestHandlerContext", + "type": "Object", + "tags": [], + "label": "requestHandlerContext", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [], "objects": [], @@ -102,6 +1681,17 @@ "tags": [], "label": "ContentManagementServerSetup", "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.ContentManagementServerSetup", + "text": "ContentManagementServerSetup" + }, + " extends ", + "CoreApi" + ], "path": "src/plugins/content_management/server/types.ts", "deprecated": false, "trackAdoption": false, @@ -598,7 +2188,7 @@ "label": "API_ENDPOINT", "description": [], "signature": [ - "\"/api/content_management\"" + "\"/api/content_management/rpc\"" ], "path": "src/plugins/content_management/common/constants.ts", "deprecated": false, @@ -651,140 +2241,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas", - "type": "Object", - "tags": [], - "label": "schemas", - "description": [], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.get", - "type": "Object", - "tags": [], - "label": "get", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.bulkGet", - "type": "Object", - "tags": [], - "label": "bulkGet", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.search", - "type": "Object", - "tags": [], - "label": "search", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ] } diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 7b367b913e7e1..0648318bbcd1e 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 46 | 0 | 46 | 3 | +| 110 | 0 | 96 | 3 | ## Client @@ -31,6 +31,18 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start +### Functions + + +### Classes + + +### Interfaces + + +### Consts, variables and types + + ## Server ### Setup @@ -39,6 +51,9 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start +### Interfaces + + ## Common ### Objects diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 92cff1862b49c..36fc563282523 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-02-28 +date: 2023-03-06 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 00fd8abf5d551..e43699f8f9566 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-02-28 +date: 2023-03-06 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 6f7f587f59345..412d135ac2c2e 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-02-28 +date: 2023-03-06 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 2c0a9a906d5d2..91602ca64a070 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-02-28 +date: 2023-03-06 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 b2be9a6510899..b52ecd105d63b 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13087,35 +13087,35 @@ }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" } ] }, @@ -13547,7 +13547,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" }, { "plugin": "securitySolution", @@ -13729,6 +13729,14 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_view.ts" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -13753,14 +13761,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -21170,7 +21170,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" }, { "plugin": "securitySolution", @@ -21352,6 +21352,14 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_view.ts" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -21376,14 +21384,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 1e4c203e60816..98bbfb15d49b5 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-02-28 +date: 2023-03-06 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 5480e56f276a5..bd1acee82bb06 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-02-28 +date: 2023-03-06 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 c2771042aa461..49f7cf2f57480 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-02-28 +date: 2023-03-06 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 75735009f549a..eb599911e5658 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-02-28 +date: 2023-03-06 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 f1e63364df52b..203632e46813d 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-02-28 +date: 2023-03-06 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 991044de8198b..e3bc106e7209c 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-02-28 +date: 2023-03-06 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 ba7f5aa586f15..01cf2f074585a 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -85,7 +85,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" }, { "plugin": "securitySolution", @@ -283,6 +283,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -299,14 +307,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -8293,7 +8293,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" }, { "plugin": "securitySolution", @@ -8491,6 +8491,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -8507,14 +8515,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -15596,7 +15596,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" }, { "plugin": "securitySolution", @@ -15794,6 +15794,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -15810,14 +15818,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -18907,6 +18907,71 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewMissingIndices", + "type": "Class", + "tags": [], + "label": "DataViewMissingIndices", + "description": [ + "\nTried to call a method that relies on SearchSource having an indexPattern assigned" + ], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewMissingIndices", + "text": "DataViewMissingIndices" + }, + " extends ", + { + "pluginId": "kibanaUtils", + "scope": "common", + "docId": "kibKibanaUtilsPluginApi", + "section": "def-common.KbnError", + "text": "KbnError" + } + ], + "path": "src/plugins/data_views/common/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewMissingIndices.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data_views/common/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewMissingIndices.Unnamed.$1", + "type": "string", + "tags": [], + "label": "message", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "dataViews", "id": "def-common.DataViewSavedObjectConflictError", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 9d6efbcd459f6..3a4a63cada7f3 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1028 | 0 | 243 | 2 | +| 1031 | 0 | 245 | 2 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 4f8549bfe3799..99b1c38a585d8 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-02-28 +date: 2023-03-06 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 a4f0482e3da88..ad9088adc2206 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -17,17 +17,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | ml, stackAlerts | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | | | home, data, esUiShared, spaces, savedObjectsManagement, fleet, observability, ml, apm, enterpriseSearch, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | -| | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, cases, fileUpload, maps, ml, infra, fleet, canvas, dashboardEnhanced, graph, monitoring, synthetics, transform, watcher, dataVisualizer, cloudSecurityPosture, securitySolution | - | +| | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, fileUpload, maps, ml, infra, fleet, canvas, dashboardEnhanced, graph, monitoring, synthetics, transform, watcher, dataVisualizer, cloudSecurityPosture, securitySolution | - | | | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, home, savedObjects, savedSearch, visualizations, dashboard, lens, ml, canvas, graph, securitySolution, synthetics, watcher, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | -| | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, presentationUtil, savedSearch, visualizations, dashboard, lens, cases, maps, ml, infra, cloudSecurityPosture, dashboardEnhanced, graph, securitySolution, synthetics, @kbn/core-saved-objects-browser-internal | - | +| | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, presentationUtil, savedSearch, visualizations, dashboard, lens, maps, ml, infra, cloudSecurityPosture, dashboardEnhanced, graph, securitySolution, synthetics, @kbn/core-saved-objects-browser-internal | - | | | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, visualizations, dashboard, ml, infra, cloudSecurityPosture, dashboardEnhanced, monitoring, synthetics, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, savedObjects, embeddable, presentationUtil, visualizations, dashboard, lens, aiops, ml, cases, maps, dataVisualizer, infra, fleet, cloudSecurityPosture, dashboardEnhanced, graph, synthetics, securitySolution, @kbn/core-saved-objects-browser-mocks | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, savedObjects, embeddable, presentationUtil, visualizations, dashboard, lens, aiops, ml, maps, dataVisualizer, infra, fleet, cloudSecurityPosture, dashboardEnhanced, graph, synthetics, securitySolution, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core-lifecycle-browser-mocks, @kbn/core, ml, dashboard, dataViews, savedSearch, @kbn/core-plugins-browser-internal | - | | | @kbn/core, savedObjects, embeddable, visualizations, dashboard, fleet, infra, canvas, graph, ml, @kbn/core-saved-objects-common, @kbn/core-saved-objects-server, actions, alerting, savedSearch, enterpriseSearch, securitySolution, taskManager, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-server | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 6a179d1e6e930..e6ccda3184032 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -382,9 +382,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 8 more | - | | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 44 more | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=savedObjects), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx#:~:text=savedObjects) | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=find) | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject)+ 1 more | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject) | - | | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObjectReference), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObjectReference) | - | | | [cases.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/cases.ts#:~:text=convertToMultiNamespaceTypeVersion), [configure.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/configure.ts#:~:text=convertToMultiNamespaceTypeVersion), [comments.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/comments.ts#:~:text=convertToMultiNamespaceTypeVersion), [user_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/user_actions.ts#:~:text=convertToMultiNamespaceTypeVersion), [connector_mappings.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | @@ -628,7 +625,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats) | - | +| | [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats) | - | @@ -1102,12 +1099,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject), [use_security_dashboards_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx#:~:text=SavedObject), [use_security_dashboards_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject) | - | | | [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | | | [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject), [use_security_dashboards_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx#:~:text=SavedObject), [use_security_dashboards_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts#:~:text=SavedObject)+ 14 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [grouped_alerts.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [grouped_alerts.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [grouped_alerts.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 5 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 5 more | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24) | 8.8.0 | @@ -1179,12 +1176,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title) | - | | | [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/stderr_logs.tsx#:~:text=indexPatternId), [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx#:~:text=indexPatternId) | - | | | [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks) | - | -| | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects)+ 1 more | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=create), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | +| | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/plugin.ts#:~:text=savedObjects) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | | | [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=bulkDelete) | - | | | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find) | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=get), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | | | [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=bulkResolve), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=bulkResolve) | - | | | [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 729cc3299b1a5..8a3f8b9c5250b 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index c6ba017a7eea2..51a0b0e7b2681 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-02-28 +date: 2023-03-06 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 928f2d7a7c7b2..69b3848cd551c 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-02-28 +date: 2023-03-06 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 e54cece7c8f15..3ef5e22e8d1cf 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-02-28 +date: 2023-03-06 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 686c30001f9f2..03c76d36654d8 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 4ac29344dbe29..f0d95e9e1edbd 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -6250,6 +6250,59 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$", + "type": "Function", + "tags": [], + "label": "shouldFetch$", + "description": [], + "signature": [ + "(updated$: ", + "Observable", + ", getInput: () => TFilterableEmbeddableInput) => ", + "Observable", + "" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$.$1", + "type": "Object", + "tags": [], + "label": "updated$", + "description": [], + "signature": [ + "Observable", + "" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$.$2", + "type": "Function", + "tags": [], + "label": "getInput", + "description": [], + "signature": [ + "() => TFilterableEmbeddableInput" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.useEmbeddableFactory", @@ -8121,6 +8174,26 @@ "path": "src/plugins/embeddable/public/plugin.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableStartDependencies.savedObjectsManagement", + "type": "Object", + "tags": [], + "label": "savedObjectsManagement", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + } + ], + "path": "src/plugins/embeddable/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -10711,6 +10784,47 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions", + "type": "Object", + "tags": [], + "label": "shouldRefreshFilterCompareOptions", + "description": [], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions.Unnamed", + "type": "Any", + "tags": [], + "label": "Unnamed", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions.state", + "type": "boolean", + "tags": [], + "label": "state", + "description": [ + "// do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results)" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "setup": { diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index e073454315134..6b89ee681ee75 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 532 | 8 | 430 | 4 | +| 539 | 9 | 436 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 6932c93c6692c..3f8f3dd13fff9 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-02-28 +date: 2023-03-06 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 eb2d18465b28d..3cf6d15fc2968 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-02-28 +date: 2023-03-06 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 b2441359f4430..f48a6359eae1c 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-02-28 +date: 2023-03-06 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 2001eb24562a0..1475dd81c37ed 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-02-28 +date: 2023-03-06 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 b8fda3d5517fc..3fef843b908ab 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-02-28 +date: 2023-03-06 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 dbada9b2dfca3..7127b9a84fcde 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 6622e021130e7..1216865d66f73 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-02-28 +date: 2023-03-06 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 8b15b75e8abbd..6647a2c2798ae 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 908f94186daf1..7f44bdc39770d 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 3765e915e81df..cc74f47f31384 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-02-28 +date: 2023-03-06 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 654925d8dc2ce..ab40a9a9f15e9 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-02-28 +date: 2023-03-06 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 668becba901ef..0aed778e6d534 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-02-28 +date: 2023-03-06 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 b0dc76f0b6f76..bc1b1e470e7ba 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-02-28 +date: 2023-03-06 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 fd80d1a9b4784..042f8572b4f00 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index cc220ea04321b..f13afe97063c3 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-02-28 +date: 2023-03-06 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 fee9dda3d70c3..7194896ca3124 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index bf5da43e550bc..c25a9e75c63d4 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-02-28 +date: 2023-03-06 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 b982d403d0459..b8e6aa731d285 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 6a3d62bd53b91..059265b23f6a9 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-02-28 +date: 2023-03-06 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 5fa1a549697f1..6d845b0a47135 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-02-28 +date: 2023-03-06 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 d8ac383049b86..31010b2e2b361 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-02-28 +date: 2023-03-06 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 c48d1e9a662a6..e4b3d263b0f57 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-02-28 +date: 2023-03-06 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 8e93da50c1e5a..b79f463fc733e 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-02-28 +date: 2023-03-06 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 3c2201044be35..3823480b372f4 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -4573,7 +4573,12 @@ "path": "src/plugins/files/server/types.ts", "deprecated": false, "trackAdoption": true, - "references": [], + "references": [ + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/files/index.ts" + } + ], "children": [ { "parentPluginId": "files", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 681850c64b442..2690af2475690 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-02-28 +date: 2023-03-06 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 da7e5cf17aa8b..c96bea73195eb 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-02-28 +date: 2023-03-06 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 6b022a38b7bd6..df2aca4d29022 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -5625,7 +5625,8 @@ "docId": "kibSpacesPluginApi", "section": "def-server.SpacesPluginStart", "text": "SpacesPluginStart" - } + }, + " | undefined" ], "path": "x-pack/plugins/fleet/server/plugin.ts", "deprecated": false, @@ -6029,6 +6030,86 @@ ], "returnComment": [] }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages", + "type": "Function", + "tags": [], + "label": "getPackages", + "description": [], + "signature": [ + "(params?: { excludeInstallStatus?: false | undefined; category?: string | undefined; prerelease?: false | undefined; } | undefined) => Promise<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackageList", + "text": "PackageList" + }, + ">" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.excludeInstallStatus", + "type": "boolean", + "tags": [], + "label": "excludeInstallStatus", + "description": [], + "signature": [ + "false | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.category", + "type": "string", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.prerelease", + "type": "boolean", + "tags": [], + "label": "prerelease", + "description": [], + "signature": [ + "false | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-server.PackageClient.reinstallEsAssets", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index e605c7ee935e0..d22e28e12edb8 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-02-28 +date: 2023-03-06 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 | |-------------------|-----------|------------------------|-----------------| -| 1089 | 3 | 984 | 27 | +| 1094 | 3 | 989 | 27 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 4cd3f5543c815..459e26866908a 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-02-28 +date: 2023-03-06 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 ccd8dc54f268d..b9c32d72f263f 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-02-28 +date: 2023-03-06 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 6e9c53480fdae..9319bf5a3672c 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-02-28 +date: 2023-03-06 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 fa1b8a61ad548..66ec11f72781d 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-02-28 +date: 2023-03-06 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 bb61d5ca51b3f..9864afe23f432 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-02-28 +date: 2023-03-06 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 5dc3493807348..0237161dca786 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-02-28 +date: 2023-03-06 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 1bce234957b6d..89d1add71d67a 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-02-28 +date: 2023-03-06 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 894e811cb6f8f..71eda0a212f5e 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-02-28 +date: 2023-03-06 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 d575dd2ad8d69..d0c0ce62ade97 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-02-28 +date: 2023-03-06 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 6daa5009ec5ce..aa1ac5e63493a 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-02-28 +date: 2023-03-06 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 938b5bd49c8b0..22efd8c4f5c8e 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-02-28 +date: 2023-03-06 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 15a61db6deb96..995dac14a07f3 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 317fbd1e1da7c..b3b0ad5237fb0 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 992e51d5ca374..0972007fbda5a 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-02-28 +date: 2023-03-06 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 0182fb114a71d..bd2ddf7e67a28 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-02-28 +date: 2023-03-06 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 9684fb67be844..d46e240b638e4 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1ba2bd42b6394..cece875d46921 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-02-28 +date: 2023-03-06 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 6d48a60192c3c..c0483c109949b 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-02-28 +date: 2023-03-06 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 b4a362286f94b..a38732f2d5503 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-02-28 +date: 2023-03-06 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 6cc59522d9f39..f7939b3ad96b2 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-02-28 +date: 2023-03-06 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 004b9f13a4dee..7bb19ca82257a 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-02-28 +date: 2023-03-06 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 a0ab0a9f8c901..ea24a6c7f85dc 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-02-28 +date: 2023-03-06 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 10fe54398cb26..1941d1d5e65ee 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-02-28 +date: 2023-03-06 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 2513fff6c64fd..2bdeb5a48904e 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index ff0b1e2cf64e5..5d07732e93a5a 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -1884,6 +1884,43 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.serviceMap", + "type": "Function", + "tags": [], + "label": "serviceMap", + "description": [], + "signature": [ + "(options: ", + "ServiceMapOpts", + ") => (timestamp: number) => ", + "Transaction", + "[]" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.serviceMap.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "ServiceMapOpts" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/apm-synthtrace-client", "id": "def-common.timerange", diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 72c107abb92cb..95e3de9e98abf 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.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 | |-------------------|-----------|------------------------|-----------------| -| 152 | 0 | 152 | 16 | +| 154 | 0 | 154 | 17 | ## Common diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 49e481e6ae3f9..26dbfded32879 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-02-28 +date: 2023-03-06 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 368f913d5c27e..5baa5aae15e6c 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-02-28 +date: 2023-03-06 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 c74cd3217452b..3aced28a3de67 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-02-28 +date: 2023-03-06 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 c793d1c5a5c10..1687897279426 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-02-28 +date: 2023-03-06 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 4daaf41d72959..98d67d040292f 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-02-28 +date: 2023-03-06 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 dccd9a9031be5..5ae1128d45747 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-02-28 +date: 2023-03-06 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 eab94ac6595e4..16b2f6cd9b702 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-02-28 +date: 2023-03-06 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 07ae40b17946c..ab868751b4a1d 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-02-28 +date: 2023-03-06 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 73a06511196e7..21d8034a05a91 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-02-28 +date: 2023-03-06 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 3787f059786a8..aa71819d12133 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-02-28 +date: 2023-03-06 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 2178f170966fc..077590dc442fe 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_code_editor_mocks.mdx index 09a047af41856..05ae8ea81ddb9 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-02-28 +date: 2023-03-06 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 620e35d139390..1726f6ac5cdcc 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-02-28 +date: 2023-03-06 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 e28a6f5f0959e..919d984101038 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-02-28 +date: 2023-03-06 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 d3c4cd303f272..beb374656c632 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-02-28 +date: 2023-03-06 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 7d646114fb442..ae82daf236ac0 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-02-28 +date: 2023-03-06 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 2172cd92ee114..3b84091e0f61c 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-02-28 +date: 2023-03-06 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 a7f6b6fb80369..f153e0aec791b 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-02-28 +date: 2023-03-06 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 fe9deb4b585ac..33a5b397cc591 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-02-28 +date: 2023-03-06 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 38fb24b930ca7..f1bcfdb80668a 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-02-28 +date: 2023-03-06 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 432774cbff830..556026280358a 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-02-28 +date: 2023-03-06 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 9c3b01c84d7de..0a2a502e2e4cc 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-02-28 +date: 2023-03-06 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 c142aabd9a321..42a385a8d40ff 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-02-28 +date: 2023-03-06 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 f868c799f5928..46b0ab6305011 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-02-28 +date: 2023-03-06 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 6f8e2526177fa..598e088bc649c 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-02-28 +date: 2023-03-06 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 8f4d90f1df324..cee5c03a76226 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-02-28 +date: 2023-03-06 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 b0c74a42b25ac..99baee68bc8b2 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-02-28 +date: 2023-03-06 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 f194c2065f746..12e14df40c9d8 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-02-28 +date: 2023-03-06 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 7ddaa26562b22..797e31cf3fd88 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-02-28 +date: 2023-03-06 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 27b03ed3a1a21..e4ad698a48297 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-02-28 +date: 2023-03-06 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 aa9b5e329e9e8..262891b3b7fa1 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-02-28 +date: 2023-03-06 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 26dbd86f7ff30..010246c8cf3c4 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-02-28 +date: 2023-03-06 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 50a40e42e8f38..7a87ed0b8425f 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-02-28 +date: 2023-03-06 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 b8f1c44efe947..f0005e3511c57 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-02-28 +date: 2023-03-06 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 2ebce3e9b4460..55fe0f74c3a91 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-02-28 +date: 2023-03-06 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 0093670745c9d..4ff6ed9a5beca 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-02-28 +date: 2023-03-06 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 2d97c57d5a937..f200b380ec4f2 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-02-28 +date: 2023-03-06 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 f6998d9b6f6bd..b613a39093a43 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-02-28 +date: 2023-03-06 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 a5164a238b129..497e7a91f40d8 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-02-28 +date: 2023-03-06 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 40a0b78ba18ea..d108b9e3e53b2 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-02-28 +date: 2023-03-06 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 9adcd8599792f..4469a752922b2 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-02-28 +date: 2023-03-06 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 3c464b4c79406..a4f69b3f2009b 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-02-28 +date: 2023-03-06 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 3065855251657..0711c40369865 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-02-28 +date: 2023-03-06 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 8ec8f5a3543ce..6b094acec8485 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-02-28 +date: 2023-03-06 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 347beed689407..0999842368433 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-02-28 +date: 2023-03-06 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 85b0225e69aea..b9c95c6d93309 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-02-28 +date: 2023-03-06 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 6f6646157d997..7671ec0b2f91b 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-02-28 +date: 2023-03-06 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 f94be732c56bb..58c5527bc226e 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-02-28 +date: 2023-03-06 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 1e6dddce80d5a..b248992190c56 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-02-28 +date: 2023-03-06 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 04072fdc8bbfe..782986bda82dc 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-02-28 +date: 2023-03-06 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 95222a6ac1824..e566546a00d07 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-02-28 +date: 2023-03-06 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 0f1e4720014ac..9176fa8c4963e 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-02-28 +date: 2023-03-06 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 c497ffb4366d2..34e57ab839fa0 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-02-28 +date: 2023-03-06 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 fb218b43f4269..f5ae0158b8ebd 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-02-28 +date: 2023-03-06 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 0d095d6f7c53d..1dd883d925a8a 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-02-28 +date: 2023-03-06 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 e2ea45eb55375..66507284f2eb1 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-02-28 +date: 2023-03-06 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 bb0d0cc31bead..2d42d19c3274b 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-02-28 +date: 2023-03-06 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 2cee9209cdbef..0b576e3cd3dc3 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-02-28 +date: 2023-03-06 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 7a30a0bed12ef..bf58ebc476bc3 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-02-28 +date: 2023-03-06 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 b815a71f4dea4..7c8d3c03741e3 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-02-28 +date: 2023-03-06 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 24e0983a06988..b7ccd23c1cd94 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 8940205ac39e7..b09ea514c2c58 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 09cf5a04b533b..3e5519d9c2a1b 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index a89837fea7fa1..ebb2fc040e312 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-02-28 +date: 2023-03-06 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 32bca8361a9f2..625f28c770ad7 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-02-28 +date: 2023-03-06 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 dd94662c3b9a4..e23343e98911f 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-02-28 +date: 2023-03-06 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 c2260e5150dd2..b4327f8ed1728 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-02-28 +date: 2023-03-06 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 5035a999507ef..11e0d9e26398e 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-02-28 +date: 2023-03-06 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 32c3f93a2147d..1a3a7017db225 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-02-28 +date: 2023-03-06 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 9e817bea42692..52b99f5216813 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-02-28 +date: 2023-03-06 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 38992012702e3..ebcca811a5c63 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-02-28 +date: 2023-03-06 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 997277de0992c..833e401875948 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-02-28 +date: 2023-03-06 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 590a32f7b6804..9e5bae11eae0d 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-02-28 +date: 2023-03-06 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 0a2e946d0ef46..9e4b547f19bf7 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-02-28 +date: 2023-03-06 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 6b4a94c7a65c0..aa87ff61c99c1 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-02-28 +date: 2023-03-06 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 9a009a36d14b0..2d5a5e1d37a45 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-02-28 +date: 2023-03-06 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 b71faf9a8d6ce..9c07847165f7f 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-02-28 +date: 2023-03-06 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 dcbda996bb75e..d89328288ea32 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-02-28 +date: 2023-03-06 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 62c46d3b36627..e3b5dadb5673c 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-02-28 +date: 2023-03-06 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 198ef483d2837..891e945990acc 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-02-28 +date: 2023-03-06 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 7f339abb6cb8a..95e054443e7d6 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-02-28 +date: 2023-03-06 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 8c04ac064bebe..8cfc6d3fe04df 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-02-28 +date: 2023-03-06 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 82dea39db37f3..25cb89b99f8be 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-02-28 +date: 2023-03-06 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 c7f2baee7037c..68cf6871c1e00 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-02-28 +date: 2023-03-06 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 5c962b28d9886..f4010eff7a3fa 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-02-28 +date: 2023-03-06 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 cce44307af439..90ff2aa0f9a3b 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-02-28 +date: 2023-03-06 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 b7547349d8917..c543a8e684193 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 24328cd2e9a6b..93fafbae8bd76 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -5006,6 +5006,20 @@ "path": "packages/core/http/core-http-server/src/router/request.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-server", + "id": "def-common.KibanaRouteOptions.access", + "type": "CompoundType", + "tags": [], + "label": "access", + "description": [], + "signature": [ + "\"internal\" | \"public\"" + ], + "path": "packages/core/http/core-http-server/src/router/request.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -6075,6 +6089,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-http-server", + "id": "def-common.RouteConfigOptions.access", + "type": "CompoundType", + "tags": [], + "label": "access", + "description": [ + "\nDefines intended request origin of the route:\n- public. The route is public, declared stable and intended for external access.\n In the future, may require an incomming request to contain a specified header.\n- internal. The route is internal and intended for internal access only.\n\nIf not declared, infers access from route path:\n- access =`internal` for '/internal' route path prefix\n- access = `public` for everything else" + ], + "signature": [ + "\"internal\" | \"public\" | undefined" + ], + "path": "packages/core/http/core-http-server/src/router/route.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-http-server", "id": "def-common.RouteConfigOptions.tags", diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index d65a953512a37..17d30277b9318 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_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 | |-------------------|-----------|------------------------|-----------------| -| 411 | 1 | 160 | 0 | +| 413 | 1 | 161 | 0 | ## Common diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 96aadf2d16be1..899dea4e6b82e 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-02-28 +date: 2023-03-06 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 489443cba0e0b..48ae8b2d28b78 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-02-28 +date: 2023-03-06 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 f50069c3f3d4a..8033745762fee 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-02-28 +date: 2023-03-06 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 d634637079eca..dcc2e2a036074 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-02-28 +date: 2023-03-06 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 ef3c68b517d8c..72a43c8231e40 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-02-28 +date: 2023-03-06 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 8b3b165d647b1..1f2438f638db8 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-02-28 +date: 2023-03-06 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 46b7c178fcc81..f483b12944680 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-02-28 +date: 2023-03-06 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 67f4789cf12a7..cf8e609c7ce76 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-02-28 +date: 2023-03-06 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 4b3c13625ec7b..b341ddff154ce 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-02-28 +date: 2023-03-06 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 ea11c4da9c361..dfce976e47870 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index 92a9bfa433892..fa4c32568aba3 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -677,10 +677,6 @@ "plugin": "discover", "path": "src/plugins/discover/public/application/main/discover_main_route.tsx" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/public/kibana_services.ts" @@ -801,10 +797,6 @@ "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" @@ -833,10 +825,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 3f09d5bddec53..9220ee41ae983 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-02-28 +date: 2023-03-06 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 8c504dfc42433..0e5b49fd726f2 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-02-28 +date: 2023-03-06 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 6e1ad00dc2109..c1493d4e0c325 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-02-28 +date: 2023-03-06 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 8866307025a39..27d75a2ab2ef0 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-02-28 +date: 2023-03-06 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 f8f39b499356f..0c775c0c9651c 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-02-28 +date: 2023-03-06 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 546977f0811d0..d328633ecba6c 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-02-28 +date: 2023-03-06 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 0b0c961bc0b74..a438d09f6746a 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-02-28 +date: 2023-03-06 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 0e38ee4bee9cb..af1f19777cd62 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-02-28 +date: 2023-03-06 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 477dc6dcfbfae..257fde0d8a385 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-02-28 +date: 2023-03-06 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 16858dd741c08..ed5387f954ea9 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-02-28 +date: 2023-03-06 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 cd7556995b5b1..f4eeb913a51b6 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-02-28 +date: 2023-03-06 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 a1e3c4bcf55e3..fba97ed8a5c08 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-02-28 +date: 2023-03-06 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 b4382c5f92369..c56346028ea92 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-02-28 +date: 2023-03-06 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 5301a90c90b28..a12d76258a746 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-02-28 +date: 2023-03-06 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 4f6bf040660eb..0e9eb40997e07 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-02-28 +date: 2023-03-06 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 972b3c1597f3c..bebcb81b47508 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-02-28 +date: 2023-03-06 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 965ec4d1acd3e..8b7a03fd9aea9 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-02-28 +date: 2023-03-06 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 ba8bd02cacbc1..b877808b83451 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_notifications_browser.mdx index fa35561e301dc..7492646d6340a 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-02-28 +date: 2023-03-06 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 1401f5adcf067..63581dad4bdfa 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-02-28 +date: 2023-03-06 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 ab530789c88d6..7dff7c85cc7fb 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-02-28 +date: 2023-03-06 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 67fbc659c9ed1..7a8fceabacea3 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-02-28 +date: 2023-03-06 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 f4a4ac81b9b4e..651c08511a346 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-02-28 +date: 2023-03-06 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 19a6cecb35764..0e8d106b63026 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-02-28 +date: 2023-03-06 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 a75e5e40ce83b..5d0cc0343d046 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-02-28 +date: 2023-03-06 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 5ca8c83b6ce61..b8fb539c2af0d 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-02-28 +date: 2023-03-06 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 2f22103dbf192..533e1c57a485e 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-02-28 +date: 2023-03-06 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 3c4c88afbd8a5..f7a53e384f8bb 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-02-28 +date: 2023-03-06 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 94fbe3c381272..93b3576d51e74 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-02-28 +date: 2023-03-06 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 4bb4a6293a868..db6d5ca0a6157 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-02-28 +date: 2023-03-06 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 f5f81a45db236..0f8ee4b5ff22d 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-02-28 +date: 2023-03-06 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 aa0d29f8cd612..edf82baefb01d 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-02-28 +date: 2023-03-06 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 ca6bc8c89dbf2..5aaf30fec42e8 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-02-28 +date: 2023-03-06 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 d33764a007ee1..7cd113e16d438 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-02-28 +date: 2023-03-06 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 47616ddb798b9..755eca219cfc1 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -1278,18 +1278,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -1558,10 +1546,6 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -2243,10 +2227,6 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/persistence/saved_objects_utils/find_object_by_title.ts" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "maps", "path": "x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx" @@ -2527,10 +2507,6 @@ "plugin": "monitoring", "path": "x-pack/plugins/monitoring/public/application/pages/elasticsearch/ingest_pipeline_modal.tsx" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -4000,50 +3976,6 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/kibana.ts" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "maps", "path": "x-pack/plugins/maps/public/maps_vis_type_alias.ts" diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 9925c7d54e924..86918610ecb1d 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 371aec9bdd901..bd3bac995c958 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; 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 52030b02bde3d..f2add4aa1b41b 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-02-28 +date: 2023-03-06 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 297fa858fc441..e2178f986cf11 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-02-28 +date: 2023-03-06 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 5b47d36f7b4e3..9560336be428e 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-02-28 +date: 2023-03-06 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 08754e85a429e..e8d938fcdf926 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-02-28 +date: 2023-03-06 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 3f72172ad2624..a8ba8a2b4c588 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-02-28 +date: 2023-03-06 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 0e2f1a128dc24..9d5905f0b037d 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-02-28 +date: 2023-03-06 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 e9cad4c0e296d..b4cdf4284433d 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-02-28 +date: 2023-03-06 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 0c231f1ff4878..62156f3b4dc5a 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -2800,6 +2800,22 @@ "plugin": "data", "path": "src/plugins/data/common/query/persistable_state.ts" }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, { "plugin": "embeddable", "path": "src/plugins/embeddable/common/lib/migrate_base_input.ts" @@ -2820,22 +2836,6 @@ "plugin": "embeddable", "path": "src/plugins/embeddable/common/lib/inject.ts" }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/public/utils/saved_visualization_references/controls_references.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index abdd31b8265e9..60d82b60201db 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-02-28 +date: 2023-03-06 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 e2eacb6b852ff..14c46866c41fa 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-02-28 +date: 2023-03-06 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 c386a8bac46dd..555256b537429 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-02-28 +date: 2023-03-06 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 0134af64ddd5c..64a0494b9127c 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 @@ -1771,37 +1771,39 @@ }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.updateTargetMappingsMeta", + "id": "def-common.updateMappings", "type": "Function", "tags": [], - "label": "updateTargetMappingsMeta", + "label": "updateMappings", "description": [ - "\nUpdates an index's mappings _meta information" + "\nUpdates an index's mappings and runs an pickupUpdatedMappings task so that the mapping\nchanges are \"picked up\". Returns a taskId to track progress." ], "signature": [ - "({ client, index, meta, }: ", - "UpdateTargetMappingsMetaParams", + "({ client, index, mappings, }: ", + "UpdateMappingsParams", ") => ", "TaskEither", "<", "RetryableEsClientError", - ", \"update_mappings_meta_succeeded\">" + " | ", + "IncompatibleMappingException", + ", \"update_mappings_succeeded\">" ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts", + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.updateTargetMappingsMeta.$1", + "id": "def-common.updateMappings.$1", "type": "Object", "tags": [], - "label": "{\n client,\n index,\n meta,\n }", + "label": "{\n client,\n index,\n mappings,\n}", "description": [], "signature": [ - "UpdateTargetMappingsMetaParams" + "UpdateMappingsParams" ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts", + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts", "deprecated": false, "trackAdoption": false, "isRequired": true 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 da9c17cfc84dc..3602f2b60e054 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-02-28 +date: 2023-03-06 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 b7ff18bfe97db..f40ba3187e9d4 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 3708f94bd910b..1ac28a274b704 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 2af33c48974f5..3f77bccfa3063 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-02-28 +date: 2023-03-06 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 c63669a42313a..dfc971cf5823a 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-02-28 +date: 2023-03-06 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 30311cf44a269..2e564b22778d1 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-02-28 +date: 2023-03-06 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 c4464831aa731..50d6615f60b93 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-02-28 +date: 2023-03-06 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 967ee90a4af04..74b2987c82c76 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-02-28 +date: 2023-03-06 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 36ab7c3c06d35..9ac2c45f88404 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-02-28 +date: 2023-03-06 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 294abb3b0c19a..37baef2241373 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-02-28 +date: 2023-03-06 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 844f75cab333a..ca35c9bf21250 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-02-28 +date: 2023-03-06 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 5639a6c086857..e17e4551f1548 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-02-28 +date: 2023-03-06 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 5c0b770e2c434..a7ed79faf22b7 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-02-28 +date: 2023-03-06 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 d9e9ac63049ae..db4caa5d0387d 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-02-28 +date: 2023-03-06 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 d9fbb1ee32328..f147e2451fad0 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-02-28 +date: 2023-03-06 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 a0448c7fa78bf..ab770cdccd31c 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-02-28 +date: 2023-03-06 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 04bfe87b1f780..0ec1e2b51a268 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-02-28 +date: 2023-03-06 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 1eb0414dd45c8..c38100781b4fe 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-02-28 +date: 2023-03-06 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 8c09095a3e3b5..32785a4616e61 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-02-28 +date: 2023-03-06 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 822f2d74702df..99217d5fe15eb 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-02-28 +date: 2023-03-06 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 28564e1638de5..805a62a464ee3 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-02-28 +date: 2023-03-06 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 14a983d39a21d..56b45eb7daa54 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_core_ui_settings_common.mdx index e7e4dc709985f..cf254112e56c0 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-02-28 +date: 2023-03-06 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 f1f131ca9ae40..9d832a41c244f 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-02-28 +date: 2023-03-06 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 322a944c92407..a6e20840c6073 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-02-28 +date: 2023-03-06 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 972e4fea27a2b..1330dc3e02137 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-02-28 +date: 2023-03-06 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 ad1a2af0c26dd..8dc4b80826870 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-02-28 +date: 2023-03-06 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 7986cb3e189ac..6b59888fe811a 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-02-28 +date: 2023-03-06 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 948602f8b16ce..585389e439927 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-02-28 +date: 2023-03-06 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 a1939bcf16477..1edd3ca0b1a1a 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-02-28 +date: 2023-03-06 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 3e6ba58a589e0..3b9482ad81832 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-02-28 +date: 2023-03-06 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 cd7c5ec1315f8..d6d991138bab6 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-02-28 +date: 2023-03-06 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 dc4e0302a3840..afe662a3a1549 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-02-28 +date: 2023-03-06 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 7701ace42bb65..cb9488430d958 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-02-28 +date: 2023-03-06 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 ee43d20b0961e..5e7bf384d2418 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-02-28 +date: 2023-03-06 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 3d9236ee8431d..732c1df04f168 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-02-28 +date: 2023-03-06 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 a4503d762f0c2..a72dfdc79bf5b 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 690befe3cbb83..64a4cdd62da99 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-02-28 +date: 2023-03-06 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 0399b4f8c746f..5f2a542588593 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 4657ed79be42d..fd6eb8fa30e8d 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-02-28 +date: 2023-03-06 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 bd5b4c146878d..0981db15c283b 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-02-28 +date: 2023-03-06 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 8fe18eca0bc56..6f2dde46f12f8 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-02-28 +date: 2023-03-06 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 ba011e6bbf167..b792720b934e1 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-02-28 +date: 2023-03-06 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 9421f34bd8b83..5e3f5ea1ebbf6 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-02-28 +date: 2023-03-06 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 fe513e762d0e9..9f3395e6b6125 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-02-28 +date: 2023-03-06 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 e0903de48be62..d71777b660a23 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-02-28 +date: 2023-03-06 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 847febd25c24d..19591ca8013ce 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-02-28 +date: 2023-03-06 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 d399dc95f270c..eae070a89754a 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-02-28 +date: 2023-03-06 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 22fbc24cf88a1..0491e5f5cf18d 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-02-28 +date: 2023-03-06 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 e6ea6c2915b92..d3f5b0ce21f8d 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-02-28 +date: 2023-03-06 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 67f7f707c8648..2d5b0f7d39e03 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-02-28 +date: 2023-03-06 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 9ac919b0a22a4..adbf2a7af0429 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-02-28 +date: 2023-03-06 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 938c566cab0ad..6592a5a667c13 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 15e3f4473cc4d..e1d0692ef6270 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-02-28 +date: 2023-03-06 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 d52887d5f386a..29c6bb93fe202 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-02-28 +date: 2023-03-06 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 2f6f53b29bcc8..de3573c4c2330 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-02-28 +date: 2023-03-06 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 3c704dc1082d2..80a1334fb086f 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-02-28 +date: 2023-03-06 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 d2449d916ce71..f4bb7c2258c70 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-02-28 +date: 2023-03-06 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 2b2abfa098b6b..b46c9aa07026b 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-02-28 +date: 2023-03-06 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 ccc93fbc3ff29..8ef0fbe48fcf2 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-02-28 +date: 2023-03-06 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 2c78806ed6e46..2db7914d855d9 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-02-28 +date: 2023-03-06 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 72bd7cf1cb0f0..43e666be6fc11 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-02-28 +date: 2023-03-06 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 50d4412ff5b81..82a38bceb46e1 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 20a8be80b5c93..84401dd00eb33 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 93621cf47e0aa..047599dafbb79 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index d742be4326672..9325f6d350bab 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 610d8fc4a18f5..fbfd3d6c3568a 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-02-28 +date: 2023-03-06 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 ff7cb5480240d..a8120973cd5f6 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-02-28 +date: 2023-03-06 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 94f433a05f257..bacb31e635cfd 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-02-28 +date: 2023-03-06 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 4e0fcbffc46f9..89bada4458380 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-02-28 +date: 2023-03-06 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 4d5f32f707390..25caf788fb1c0 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-02-28 +date: 2023-03-06 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 6d92d1ead30b5..2c13494930a2b 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-02-28 +date: 2023-03-06 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 7a15ccfa87ed5..72605d7e93e37 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-02-28 +date: 2023-03-06 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 3a50b4adcfb53..4ee51bce1de26 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_ml_date_picker.devdocs.json index 881361d0e70db..164c1d688fba4 100644 --- a/api_docs/kbn_ml_date_picker.devdocs.json +++ b/api_docs/kbn_ml_date_picker.devdocs.json @@ -1025,6 +1025,29 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/ml-date-picker", + "id": "def-common.FullTimeRangeSelectorProps.apiPath", + "type": "CompoundType", + "tags": [], + "label": "apiPath", + "description": [ + "\nOptional API path." + ], + "signature": [ + { + "pluginId": "@kbn/ml-date-picker", + "scope": "common", + "docId": "kibKbnMlDatePickerPluginApi", + "section": "def-common.SetFullTimeRangeApiPath", + "text": "SetFullTimeRangeApiPath" + }, + " | undefined" + ], + "path": "x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1196,6 +1219,23 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ml-date-picker", + "id": "def-common.SetFullTimeRangeApiPath", + "type": "Type", + "tags": [], + "label": "SetFullTimeRangeApiPath", + "description": [ + "\nAllowed API paths to be passed to `setFullTimeRange`." + ], + "signature": [ + "\"/internal/file_upload/time_field_range\" | \"/api/ml/fields_service/time_field_range\"" + ], + "path": "x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index dfeeb1510c9f7..0887f2520231b 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.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 | |-------------------|-----------|------------------------|-----------------| -| 50 | 0 | 4 | 0 | +| 52 | 0 | 4 | 0 | ## Common diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 6874332a26f30..d34393f3b933b 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-02-28 +date: 2023-03-06 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 6670e94eb4eee..8880ada0ea734 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-02-28 +date: 2023-03-06 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 bbd025fe1fa5a..d646f7255ffab 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-02-28 +date: 2023-03-06 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 c0554041a68a5..369d2436b8bc6 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-02-28 +date: 2023-03-06 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_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index be947c303ff24..5199bf99beb9a 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-02-28 +date: 2023-03-06 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 12f3acbcf3dd9..6a756ccda7764 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-02-28 +date: 2023-03-06 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_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 6a6b54987f4c0..6e03aecad1fdd 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-02-28 +date: 2023-03-06 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 78a9557f3a8e2..645a7afa94e0c 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 796787cdff114..8e87c34fcd17b 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-02-28 +date: 2023-03-06 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 4c2ced69cd354..7fdb8ff118423 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-02-28 +date: 2023-03-06 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 fcd4299d7dec6..6182dae7babb3 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-02-28 +date: 2023-03-06 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 8735a6ebf7771..b1e94b730266c 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-02-28 +date: 2023-03-06 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 2fe0e395a926a..5c56c5257f4ed 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-02-28 +date: 2023-03-06 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 b42e4ff694c8e..c646baef008b9 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-02-28 +date: 2023-03-06 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 26651728d35af..821949aec651a 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-02-28 +date: 2023-03-06 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 97902ffe415b4..30dfe830d79cb 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-02-28 +date: 2023-03-06 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 90bce3b337ef4..d930490c65b87 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-02-28 +date: 2023-03-06 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 ea301fcb7bf65..30eaec2fec5ae 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-02-28 +date: 2023-03-06 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 f027a5abaee44..234d0612eeb21 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 67b113801852c..6474139f1e2c8 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 314a0e7da1a48..f205491dd4b51 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 5a8bc014bed9c..1c7832cb30807 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-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 5ee5d919296b0..6c224f1566189 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index bd64b1db81bfc..282e158840e9e 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 85aceb9690e36..2fe9361305cc2 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_securitysolution_grouping.devdocs.json new file mode 100644 index 0000000000000..0eb4ab29c8175 --- /dev/null +++ b/api_docs/kbn_securitysolution_grouping.devdocs.json @@ -0,0 +1,279 @@ +{ + "id": "@kbn/securitysolution-grouping", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGrouping", + "type": "Function", + "tags": [], + "label": "getGrouping", + "description": [], + "signature": [ + "(props: ", + "GroupingProps", + ") => React.ReactElement<", + "GroupingProps", + ", string | React.JSXElementConstructor>" + ], + "path": "packages/kbn-securitysolution-grouping/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGrouping.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "GroupingProps" + ], + "path": "packages/kbn-securitysolution-grouping/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGroupingQuery", + "type": "Function", + "tags": [], + "label": "getGroupingQuery", + "description": [], + "signature": [ + "({ additionalFilters, additionalAggregationsRoot, additionalStatsAggregationsFields0, additionalStatsAggregationsFields1, from, runtimeMappings, stackByMultipleFields0, stackByMultipleFields0Size, stackByMultipleFields0From, stackByMultipleFields0Sort, stackByMultipleFields1, stackByMultipleFields1Size, stackByMultipleFields1From, stackByMultipleFields1Sort, to, }: ", + "GroupingQueryArgs", + ") => ", + "GroupingQuery" + ], + "path": "packages/kbn-securitysolution-grouping/src/containers/query/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGroupingQuery.$1", + "type": "Object", + "tags": [], + "label": "{\n additionalFilters = [],\n additionalAggregationsRoot,\n additionalStatsAggregationsFields0,\n additionalStatsAggregationsFields1,\n from,\n runtimeMappings,\n stackByMultipleFields0,\n stackByMultipleFields0Size = DEFAULT_STACK_BY_FIELD0_SIZE,\n stackByMultipleFields0From,\n stackByMultipleFields0Sort,\n stackByMultipleFields1,\n stackByMultipleFields1Size = DEFAULT_STACK_BY_FIELD1_SIZE,\n stackByMultipleFields1From,\n stackByMultipleFields1Sort,\n to,\n}", + "description": [], + "signature": [ + "GroupingQueryArgs" + ], + "path": "packages/kbn-securitysolution-grouping/src/containers/query/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGroupSelector", + "type": "Function", + "tags": [], + "label": "getGroupSelector", + "description": [], + "signature": [ + "(props: ", + "GroupSelectorProps", + ") => React.ReactElement<", + "GroupSelectorProps", + ", string | React.JSXElementConstructor>" + ], + "path": "packages/kbn-securitysolution-grouping/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.getGroupSelector.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "GroupSelectorProps" + ], + "path": "packages/kbn-securitysolution-grouping/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.isNoneGroup", + "type": "Function", + "tags": [], + "label": "isNoneGroup", + "description": [], + "signature": [ + "(groupKey: string) => boolean" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.isNoneGroup.$1", + "type": "string", + "tags": [], + "label": "groupKey", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.GroupingAggregation", + "type": "Interface", + "tags": [], + "label": "GroupingAggregation", + "description": [ + "Defines the shape of the aggregation returned by Elasticsearch" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.GroupingAggregation.stackByMultipleFields0", + "type": "Object", + "tags": [], + "label": "stackByMultipleFields0", + "description": [], + "signature": [ + "{ buckets?: ", + { + "pluginId": "@kbn/securitysolution-grouping", + "scope": "common", + "docId": "kibKbnSecuritysolutionGroupingPluginApi", + "section": "def-common.RawBucket", + "text": "RawBucket" + }, + "[] | undefined; } | undefined" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.GroupingAggregation.groupsCount0", + "type": "Object", + "tags": [], + "label": "groupsCount0", + "description": [], + "signature": [ + "{ value?: number | null | undefined; } | undefined" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.GroupingFieldTotalAggregation", + "type": "Type", + "tags": [], + "label": "GroupingFieldTotalAggregation", + "description": [], + "signature": [ + "{ [x: string]: { value?: number | null | undefined; buckets?: { doc_count?: number | null | undefined; }[] | undefined; }; }" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.NamedAggregation", + "type": "Type", + "tags": [], + "label": "NamedAggregation", + "description": [], + "signature": [ + "{ [x: string]: ", + "AggregationsAggregationContainer", + "; }" + ], + "path": "packages/kbn-securitysolution-grouping/src/containers/query/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.RawBucket", + "type": "Type", + "tags": [], + "label": "RawBucket", + "description": [], + "signature": [ + "GenericBuckets", + " & { alertsCount?: { value?: number | null | undefined; } | undefined; severitiesSubAggregation?: { buckets?: ", + "GenericBuckets", + "[] | undefined; } | undefined; countSeveritySubAggregation?: { value?: number | null | undefined; } | undefined; usersCountAggregation?: { value?: number | null | undefined; } | undefined; hostsCountAggregation?: { value?: number | null | undefined; } | undefined; rulesCountAggregation?: { value?: number | null | undefined; } | undefined; ruleTags?: { doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; buckets?: ", + "GenericBuckets", + "[] | undefined; } | undefined; stackByMultipleFields1?: { buckets?: ", + "GenericBuckets", + "[] | undefined; doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; } | undefined; }" + ], + "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx new file mode 100644 index 0000000000000..5b71f6be2364d --- /dev/null +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSecuritysolutionGroupingPluginApi +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-03-06 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] +--- +import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; + + + +Contact [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 13 | 5 | + +## Common + +### Functions + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 5bc067d5e6340..e18e8e9384dc3 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-02-28 +date: 2023-03-06 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 9864d4070a64f..127267136294e 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-02-28 +date: 2023-03-06 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.devdocs.json b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json index 858be46e1f188..cbd27335f2f35 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json @@ -2624,7 +2624,7 @@ "label": "CreateEndpointListItemSchema", "description": [], "signature": [ - "{ description: string; entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; expire_time?: string | undefined; }" + "{ description: string; entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts", "deprecated": false, @@ -2639,7 +2639,7 @@ "label": "CreateEndpointListItemSchemaDecoded", "description": [], "signature": [ - "Omit<{ description: string; entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; name: string; type: \"simple\"; comments: { comment: string; }[] | undefined; item_id: string | undefined; meta: object | undefined; os_types: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags: string[] | undefined; expire_time: string | undefined; }, \"entries\" | \"tags\" | \"comments\" | \"expire_time\" | \"item_id\" | \"os_types\"> & { comments: { comment: string; }[]; tags: string[]; item_id: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; os_types: (\"windows\" | \"linux\" | \"macos\")[]; expire_time: string | undefined; }" + "Omit<{ description: string; entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; name: string; type: \"simple\"; comments: { comment: string; }[] | undefined; item_id: string | undefined; meta: object | undefined; os_types: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags: string[] | undefined; }, \"entries\" | \"tags\" | \"comments\" | \"item_id\" | \"os_types\"> & { comments: { comment: string; }[]; tags: string[]; item_id: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; os_types: (\"windows\" | \"linux\" | \"macos\")[]; }" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts", "deprecated": false, @@ -5114,7 +5114,7 @@ "label": "UpdateEndpointListItemSchema", "description": [], "signature": [ - "{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; name: string; type: \"simple\"; } & { _version?: string | undefined; comments?: ({ comment: string; } & { id?: string | undefined; })[] | undefined; id?: string | undefined; item_id?: string | undefined; meta?: object | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; expire_time?: string | undefined; }" + "{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; name: string; type: \"simple\"; } & { _version?: string | undefined; comments?: ({ comment: string; } & { id?: string | undefined; })[] | undefined; id?: string | undefined; item_id?: string | undefined; meta?: object | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts", "deprecated": false, @@ -5129,7 +5129,7 @@ "label": "UpdateEndpointListItemSchemaDecoded", "description": [], "signature": [ - "Omit<{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; name: string; type: \"simple\"; _version: string | undefined; comments: ({ comment: string; } & { id?: string | undefined; })[] | undefined; id: string | undefined; item_id: string | undefined; meta: object | undefined; os_types: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags: string[] | undefined; expire_time: string | undefined; }, \"entries\" | \"tags\" | \"comments\" | \"expire_time\"> & { comments: ({ comment: string; } & { id?: string | undefined; })[]; tags: string[]; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; os_types: (\"windows\" | \"linux\" | \"macos\")[]; expire_time: string | undefined; }" + "Omit<{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; name: string; type: \"simple\"; _version: string | undefined; comments: ({ comment: string; } & { id?: string | undefined; })[] | undefined; id: string | undefined; item_id: string | undefined; meta: object | undefined; os_types: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags: string[] | undefined; }, \"entries\" | \"tags\" | \"comments\"> & { comments: ({ comment: string; } & { id?: string | undefined; })[]; tags: string[]; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; os_types: (\"windows\" | \"linux\" | \"macos\")[]; }" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts", "deprecated": false, @@ -5624,13 +5624,7 @@ "UndefinedC", "]>; tags: ", "Type", - "; expire_time: ", - "UnionC", - "<[", - "Type", - ", ", - "UndefinedC", - "]>; }>>]>" + "; }>>]>" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts", "deprecated": false, @@ -11027,13 +11021,7 @@ "UndefinedC", "]>; tags: ", "Type", - "; expire_time: ", - "UnionC", - "<[", - "Type", - ", ", - "UndefinedC", - "]>; }>>]>" + "; }>>]>" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index f2095245ff1ac..f635c7ba6c21b 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-02-28 +date: 2023-03-06 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 5668e299d77f0..60deb92356cb0 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-02-28 +date: 2023-03-06 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 d8d0c9c9871d8..548efb03d2058 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-02-28 +date: 2023-03-06 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 82a67efe6b38a..bcbdab8cab012 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-02-28 +date: 2023-03-06 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 e0f50cd3efed4..78445e2b49f41 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-02-28 +date: 2023-03-06 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 cf55a63421c8d..9193f23315a77 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-02-28 +date: 2023-03-06 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 73199675135f7..c88fee744295d 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-02-28 +date: 2023-03-06 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 d8cc6bdea546d..72e39ff5afae3 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-02-28 +date: 2023-03-06 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 be04da8990108..e85ce98a2a803 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-02-28 +date: 2023-03-06 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 b870995270f14..0f8ba0e6a041d 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-02-28 +date: 2023-03-06 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 e2f2fa7144b85..d27e52d43774f 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-02-28 +date: 2023-03-06 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 733dda4175166..efb02f34519d1 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-02-28 +date: 2023-03-06 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 e58c594bf32bf..86aa56a3dbe05 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-02-28 +date: 2023-03-06 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 2269ceb340140..49a307abf7b04 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 8e47740cb12e6..a58272efa2d1d 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-02-28 +date: 2023-03-06 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 07e493a7cb81a..f8a48097cfee2 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-02-28 +date: 2023-03-06 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 0c681b7541cb9..faa396a32ef15 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 606d8505db64b..776ee833c0f91 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 168ffd1305214..a99550f334e9d 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-02-28 +date: 2023-03-06 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 df21948c046b9..d01917282f0bb 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-02-28 +date: 2023-03-06 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 17a197cb50150..296253d39ca26 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-02-28 +date: 2023-03-06 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 4ca4a262aac6c..1c0c148a1886a 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-02-28 +date: 2023-03-06 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 3bd8feca9cc06..77395553fe48c 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-02-28 +date: 2023-03-06 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 14e59f85eee8b..eaf6ea6651f52 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-02-28 +date: 2023-03-06 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 f4abb5309c5e6..bdcb3b3d31688 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-02-28 +date: 2023-03-06 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 8209bfcad95c2..8c72b124aea00 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-02-28 +date: 2023-03-06 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 4a7f1131800f7..db965074e0a21 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-02-28 +date: 2023-03-06 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 b769fcf74f510..126a90b0138c0 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-02-28 +date: 2023-03-06 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 3e784736600a3..dfb7420456562 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-02-28 +date: 2023-03-06 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 fc64a58d11b5b..18aafaef26d63 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-02-28 +date: 2023-03-06 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 940644b3fd59d..5317c3ea7ea6e 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index b202f9f8b5870..5fc4f57d29394 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-02-28 +date: 2023-03-06 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 156eda71b5a8c..d5552f3871a81 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-02-28 +date: 2023-03-06 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 0d4be286761e6..6a817184730f6 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-02-28 +date: 2023-03-06 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 c6f9fc4ef80ad..2feb61cd40bb2 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-02-28 +date: 2023-03-06 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 ae05839415131..b5db61e3c7a53 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-02-28 +date: 2023-03-06 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 250d876c321e9..8b3c9392153f6 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-02-28 +date: 2023-03-06 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 724150dcc5ac9..7826a6f575257 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-02-28 +date: 2023-03-06 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 7b1b989ba6cee..460131cdca455 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-02-28 +date: 2023-03-06 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 5727efb34acce..9089e2600e2e6 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-02-28 +date: 2023-03-06 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 c6af6866069c9..cd47867e60301 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-02-28 +date: 2023-03-06 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 87981ddb8777f..7d6057cf4b145 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-02-28 +date: 2023-03-06 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 5ba46cdcc14c4..f8b79231463d2 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-02-28 +date: 2023-03-06 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 4a0264e15a351..214f317c962cd 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-02-28 +date: 2023-03-06 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 f8f479bc82736..2b371835720a8 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-02-28 +date: 2023-03-06 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 e71df0240d9a5..9dd45a3056268 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-02-28 +date: 2023-03-06 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 c36b9563d5951..855354f61aec4 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-02-28 +date: 2023-03-06 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 060a5d9374f60..f90c5682e758e 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-02-28 +date: 2023-03-06 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 7aa8b9b5469d8..e984dc4c62b31 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-02-28 +date: 2023-03-06 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 302f019a118b7..84ff87797af7c 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-02-28 +date: 2023-03-06 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 61068adab2e94..e218614ec5d0d 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-02-28 +date: 2023-03-06 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 a725bef6d1dfd..044bfb7cf3396 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-02-28 +date: 2023-03-06 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 26d5e2905557a..32081d60b7ab6 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-02-28 +date: 2023-03-06 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 ebaaad4fe200f..69ccf7d0f1c6b 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-02-28 +date: 2023-03-06 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 bc9fc88083646..4fbe4cca94e2d 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-02-28 +date: 2023-03-06 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 8d729da1d0c13..02c80c4474171 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-02-28 +date: 2023-03-06 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 af4f7c3196754..8099f409043ec 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-02-28 +date: 2023-03-06 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 8ac5a54019cef..fbdd83ade4809 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-02-28 +date: 2023-03-06 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 2e5cc062ce04f..f8482148a71de 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-02-28 +date: 2023-03-06 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 01ab9a6025585..40824b28e0ae6 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-02-28 +date: 2023-03-06 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 b87d54e58c159..33c0c1138d1e6 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-02-28 +date: 2023-03-06 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 db3164da08dfc..bef686985702a 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-02-28 +date: 2023-03-06 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 458810012a7dd..c91ee2bd7dbe5 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-02-28 +date: 2023-03-06 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 d58966203073b..16c5bd0d294ad 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-02-28 +date: 2023-03-06 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 68704c5518edc..aa18030812380 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-02-28 +date: 2023-03-06 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 125fb40ae685c..ffcb4a5f9bf86 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-02-28 +date: 2023-03-06 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.mdx b/api_docs/kbn_user_profile_components.mdx index 6e7c773415301..dd016f5f64a64 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 0c7dc2488443a..6cc5100729aeb 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 0d8c7b33c2bd6..c1b87ee584570 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 3e65e26219007..75c9121310fb8 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index f78260916241f..516de9bad2f70 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 6c0fb6464f98c..5707659a03c2b 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index f381f2825210d..8adc67c9a31e6 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 58fc492ba9ca5..479215657cada 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index dccc3f7001254..4997ef5ea433c 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index c33e99100df1e..3f276970b799f 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 23daa345f6194..919c0ac83f1e4 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 2d3259a4aee8b..d37ef16a7e9a3 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 3038e387bec74..4744ba9290ca5 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.devdocs.json b/api_docs/lists.devdocs.json index fe0cdc54cd46b..60eab49b1edaa 100644 --- a/api_docs/lists.devdocs.json +++ b/api_docs/lists.devdocs.json @@ -698,7 +698,7 @@ "\nThis is the same as \"createListItem\" except it applies specifically to the agnostic endpoint list and will\nauto-call the \"createEndpointList\" for you so that you have the best chance of the agnostic endpoint\nbeing there and existing before the item is inserted into the agnostic endpoint list." ], "signature": [ - "({ comments, description, entries, expireTime, itemId, meta, name, osTypes, tags, type, }: ", + "({ comments, description, entries, itemId, meta, name, osTypes, tags, type, }: ", "CreateEndpointListItemOptions", ") => Promise<{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; expire_time: string | undefined; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; }>" ], @@ -711,7 +711,7 @@ "id": "def-server.ExceptionListClient.createEndpointListItem.$1", "type": "Object", "tags": [], - "label": "{\n comments,\n description,\n entries,\n expireTime,\n itemId,\n meta,\n name,\n osTypes,\n tags,\n type,\n }", + "label": "{\n comments,\n description,\n entries,\n itemId,\n meta,\n name,\n osTypes,\n tags,\n type,\n }", "description": [], "signature": [ "CreateEndpointListItemOptions" @@ -774,7 +774,7 @@ "\nThis is the same as \"updateExceptionListItem\" except it applies specifically to the endpoint list and will\nauto-call the \"createEndpointList\" for you so that you have the best chance of the endpoint\nbeing there if it did not exist before. If the list did not exist before, then creating it here will still cause a\nreturn of null but at least the list exists again." ], "signature": [ - "({ _version, comments, description, entries, expireTime, id, itemId, meta, name, osTypes, tags, type, }: ", + "({ _version, comments, description, entries, id, itemId, meta, name, osTypes, tags, type, }: ", "UpdateEndpointListItemOptions", ") => Promise<{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; expire_time: string | undefined; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; } | null>" ], @@ -787,7 +787,7 @@ "id": "def-server.ExceptionListClient.updateEndpointListItem.$1", "type": "Object", "tags": [], - "label": "{\n _version,\n comments,\n description,\n entries,\n expireTime,\n id,\n itemId,\n meta,\n name,\n osTypes,\n tags,\n type,\n }", + "label": "{\n _version,\n comments,\n description,\n entries,\n id,\n itemId,\n meta,\n name,\n osTypes,\n tags,\n type,\n }", "description": [], "signature": [ "UpdateEndpointListItemOptions" @@ -1011,7 +1011,7 @@ "tags": [], "label": "updateExceptionListItem", "description": [ - "\nUpdate an existing exception list item" + "\nUpdate an existing exception list item\n\nNOTE: This method will PATCH the targeted exception list item, not fully overwrite it.\nAny undefined fields passed in will not be changed in the existing record. To unset any\nfields use the `updateOverwriteExceptionListItem` method\n" ], "signature": [ "({ _version, comments, description, entries, expireTime, id, itemId, meta, name, namespaceType, osTypes, tags, type, }: ", @@ -1054,6 +1054,56 @@ "the updated exception list item or null if none exists" ] }, + { + "parentPluginId": "lists", + "id": "def-server.ExceptionListClient.updateOverwriteExceptionListItem", + "type": "Function", + "tags": [], + "label": "updateOverwriteExceptionListItem", + "description": [ + "\nUpdate an existing exception list item using the overwrite method in order to behave\nmore like a PUT request rather than a PATCH request.\n\nThis was done in order to correctly unset types via update which cannot be accomplished\nusing the regular `updateExceptionItem` method. All other results of the methods are identical\n" + ], + "signature": [ + "({ _version, comments, description, entries, expireTime, id, itemId, meta, name, namespaceType, osTypes, tags, type, }: ", + { + "pluginId": "lists", + "scope": "server", + "docId": "kibListsPluginApi", + "section": "def-server.UpdateExceptionListItemOptions", + "text": "UpdateExceptionListItemOptions" + }, + ") => Promise<{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"binary\" | \"keyword\" | \"text\" | \"date\" | \"date_nanos\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"half_float\" | \"double\" | \"integer_range\" | \"float_range\" | \"long_range\" | \"double_range\" | \"date_range\" | \"ip_range\" | \"shape\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; expire_time: string | undefined; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; } | null>" + ], + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lists", + "id": "def-server.ExceptionListClient.updateOverwriteExceptionListItem.$1", + "type": "Object", + "tags": [], + "label": "{\n _version,\n comments,\n description,\n entries,\n expireTime,\n id,\n itemId,\n meta,\n name,\n namespaceType,\n osTypes,\n tags,\n type,\n }", + "description": [], + "signature": [ + { + "pluginId": "lists", + "scope": "server", + "docId": "kibListsPluginApi", + "section": "def-server.UpdateExceptionListItemOptions", + "text": "UpdateExceptionListItemOptions" + } + ], + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "the updated exception list item or null if none exists" + ] + }, { "parentPluginId": "lists", "id": "def-server.ExceptionListClient.deleteExceptionListItem", diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index d25c61a4e27c9..67ba5541755ee 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution-platform](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 208 | 0 | 93 | 51 | +| 210 | 0 | 94 | 51 | ## Client diff --git a/api_docs/management.mdx b/api_docs/management.mdx index d416a6004f48d..cd8e01d9c84ab 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index a9dd4fac83ae3..f5b00c78a59d4 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -834,10 +834,10 @@ }, { "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getFilters", + "id": "def-public.MapEmbeddable._getInputFilters", "type": "Function", "tags": [], - "label": "_getFilters", + "label": "_getInputFilters", "description": [], "signature": [ "() => ", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 18079ba00f748..ea2d35817d07f 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 4615a906a1cd7..3fd5097befc64 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index fbfca60b77b13..f4575ed364ef7 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 886a4ce48b798..52f3a8bcc3e58 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index f3b8c8d1be282..3ea14465f3b8b 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 838c42c39dbb3..a07221cc776e2 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 75c74e1f19b30..f559c31f6cde2 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 4d8591294aa9b..fa71093027187 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 15c641bc7406a..300eaaab7770e 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -2275,6 +2275,45 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-public.ChartData", + "type": "Interface", + "tags": [], + "label": "ChartData", + "description": [], + "path": "x-pack/plugins/observability/public/typings/slo/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-public.ChartData.key", + "type": "number", + "tags": [], + "label": "key", + "description": [], + "path": "x-pack/plugins/observability/public/typings/slo/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ChartData.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/observability/public/typings/slo/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-public.ConfigProps", @@ -4552,7 +4591,8 @@ "docId": "kibSpacesPluginApi", "section": "def-public.SpacesApi", "text": "SpacesApi" - } + }, + " | undefined" ], "path": "x-pack/plugins/observability/public/plugin.ts", "deprecated": false, diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index d2af532fc95fe..618d97790f52c 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 625 | 43 | 619 | 34 | +| 628 | 43 | 622 | 34 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 04f0bad23e2fe..af127f06dd2c0 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index e548f18220b28..22a1c83117223 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 576 | 472 | 38 | +| 577 | 473 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 67775 | 514 | 58529 | 1233 | +| 67915 | 515 | 58627 | 1240 | ## Plugin Directory @@ -35,19 +35,19 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 89 | 1 | 74 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 92 | 0 | 75 | 28 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 94 | 0 | 77 | 28 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 270 | 16 | 255 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 41 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 1 | 0 | 0 | 0 | | | [@elastic/platform-onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Static migration page where self-managed users can see text/copy about migrating to Elastic Cloud | 8 | 1 | 8 | 1 | -| | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | Defend for Containers | 2 | 0 | 2 | 0 | +| | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | Defend for containers (D4C) | 15 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. | 12 | 0 | 0 | 0 | | cloudFullStory | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry. | 0 | 0 | 0 | 0 | | cloudGainsight | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers Gainsight as a shipper for telemetry. | 0 | 0 | 0 | 0 | | cloudLinks | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | The cloud security posture plugin | 17 | 0 | 2 | 2 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 13 | 0 | 13 | 1 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 46 | 0 | 46 | 3 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 110 | 0 | 96 | 3 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 272 | 0 | 268 | 11 | | crossClusterReplication | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | customBranding | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Enables customization of Kibana | 0 | 0 | 0 | 0 | @@ -58,13 +58,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1028 | 0 | 243 | 2 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1031 | 0 | 245 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 10 | 0 | 8 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 78 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 532 | 8 | 430 | 4 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 539 | 9 | 436 | 4 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -90,7 +90,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 214 | 0 | 10 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1089 | 3 | 984 | 27 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1094 | 3 | 989 | 27 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -116,7 +116,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | -| | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 208 | 0 | 93 | 51 | +| | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 210 | 0 | 94 | 51 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 41 | 0 | 41 | 6 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 267 | 0 | 266 | 27 | @@ -127,7 +127,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 0 | 34 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 625 | 43 | 619 | 34 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 628 | 43 | 622 | 34 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 7 | | painlessLab | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 202 | 7 | 146 | 12 | @@ -137,8 +137,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 258 | 0 | 229 | 13 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 24 | 0 | 19 | 2 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 216 | 2 | 175 | 5 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 39 | 0 | 39 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 231 | 2 | 180 | 5 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 44 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 154 | 0 | 140 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 79 | 0 | 73 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 100 | 0 | 52 | 1 | @@ -169,7 +169,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 206 | 0 | 140 | 9 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 267 | 0 | 242 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 61 | 0 | 23 | 1 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 134 | 2 | 99 | 20 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 135 | 2 | 100 | 20 | | upgradeAssistant | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | urlDrilldown | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 12 | 0 | 12 | 0 | @@ -210,7 +210,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 18 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 27 | 0 | 27 | 3 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 152 | 0 | 152 | 16 | +| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 154 | 0 | 154 | 17 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-qa](https://github.com/orgs/elastic/teams/kibana-qa) | - | 10 | 0 | 10 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 19 | 0 | 17 | 0 | @@ -298,7 +298,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 26 | 6 | 26 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 411 | 1 | 160 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 413 | 1 | 161 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 55 | 0 | 49 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 41 | 0 | 40 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 2 | 0 | @@ -429,7 +429,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 1 | 1 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 534 | 1 | 1 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 83 | 2 | 59 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 50 | 0 | 4 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 52 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 2 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 5 | 0 | 3 | 0 | @@ -455,6 +455,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 341 | 1 | 337 | 32 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 67 | 0 | 61 | 1 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 104 | 0 | 93 | 1 | +| | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 14 | 0 | 13 | 5 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 15 | 0 | 7 | 0 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 140 | 0 | 121 | 0 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 516 | 1 | 503 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index a9ab65763cfb8..906a26347cc0c 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index deba0cdcd5085..b4da74f97f3f9 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index aaab91fddc3a4..a0816e2b57797 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 9dfd0ab61a8ae..68b53cb7cc2e0 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index a2a19da59470f..b6cbdb841f18a 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index bafd718f19619..9f147212a1e5e 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index ff7aa047cba3c..dd37ca1773244 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.devdocs.json b/api_docs/saved_objects.devdocs.json index b6ed1819ec684..366db1e5b33db 100644 --- a/api_docs/saved_objects.devdocs.json +++ b/api_docs/saved_objects.devdocs.json @@ -499,6 +499,195 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps", + "type": "Function", + "tags": [], + "label": "euiFormRowProps", + "description": [], + "signature": [ + "Requireable", + "" + ], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "{ [key: string]: any; }" + ], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$2", + "type": "string", + "tags": [], + "label": "propName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$3", + "type": "string", + "tags": [], + "label": "componentName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$4", + "type": "string", + "tags": [], + "label": "location", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$5", + "type": "string", + "tags": [], + "label": "propFullName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps", + "type": "Function", + "tags": [], + "label": "euiFieldSearchProps", + "description": [], + "signature": [ + "Requireable", + "" + ], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "{ [key: string]: any; }" + ], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$2", + "type": "string", + "tags": [], + "label": "propName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$3", + "type": "string", + "tags": [], + "label": "componentName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$4", + "type": "string", + "tags": [], + "label": "location", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$5", + "type": "string", + "tags": [], + "label": "propFullName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps", + "type": "Object", + "tags": [], + "label": "defaultProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps.euiFormRowProps", + "type": "Object", + "tags": [], + "label": "euiFormRowProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps.euiFieldSearchProps", + "type": "Object", + "tags": [], + "label": "euiFieldSearchProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [] } ] }, diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 16d7f54784ed1..0c839180055cf 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.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 | |-------------------|-----------|------------------------|-----------------| -| 216 | 2 | 175 | 5 | +| 231 | 2 | 180 | 5 | ## Client diff --git a/api_docs/saved_objects_finder.devdocs.json b/api_docs/saved_objects_finder.devdocs.json index 71969f4a97a60..08991612ff496 100644 --- a/api_docs/saved_objects_finder.devdocs.json +++ b/api_docs/saved_objects_finder.devdocs.json @@ -3,6 +3,149 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder", + "type": "Function", + "tags": [], + "label": "getSavedObjectFinder", + "description": [], + "signature": [ + "(uiSettings: ", + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + }, + ", http: ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + }, + ", savedObjectsManagement: ", + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + }, + ", savedObjectsTagging?: ", + { + "pluginId": "savedObjectsTaggingOss", + "scope": "public", + "docId": "kibSavedObjectsTaggingOssPluginApi", + "section": "def-public.SavedObjectsTaggingApi", + "text": "SavedObjectsTaggingApi" + }, + " | undefined) => (props: ", + { + "pluginId": "savedObjectsFinder", + "scope": "public", + "docId": "kibSavedObjectsFinderPluginApi", + "section": "def-public.SavedObjectFinderProps", + "text": "SavedObjectFinderProps" + }, + ") => JSX.Element" + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$1", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$2", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$3", + "type": "Object", + "tags": [], + "label": "savedObjectsManagement", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$4", + "type": "Object", + "tags": [], + "label": "savedObjectsTagging", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsTaggingOss", + "scope": "public", + "docId": "kibSavedObjectsTaggingOssPluginApi", + "section": "def-public.SavedObjectsTaggingApi", + "text": "SavedObjectsTaggingApi" + }, + " | undefined" + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "savedObjectsFinder", "id": "def-public.SavedObjectFinder", diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index ace1059c4bed5..01ac5241b7496 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 39 | 0 | 39 | 0 | +| 44 | 0 | 44 | 0 | ## Client diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 6fd979ecdbb86..2dba3826fc2de 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 18541c12418b1..5a478a5d72d05 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 442d516371a8d..3b7fbf4c34c30 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 2ddbf2b3f440a..0d73b05144331 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index fbab93eac15de..6e95730e6ddb6 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 5a9f3c7f4a93e..a9eefc59769f8 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 913ea2418a097..c8960791d4c27 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index eeced72ec06fe..c84504aa211ee 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 597c0533242ca..aa708b39f85ae 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index ce9b271f3639c..aae4d8df611ca 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 5ffe5a5072c6f..bca4705c39339 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index f05bf8794e108..28c13cf146473 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 53063d23c2cc2..0020dd50e5400 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 7f1f52fa198db..c0b544d6196a0 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index aa72caf8ee862..dbc2a0c30c002 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index ead6ab9d2fc06..96c649851eaab 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index bea30d56e7f3a..bcbd4fbac39a2 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 2eaa03df603b3..51080e003eaf0 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index e445e28aa62ef..8b91983a210c3 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 14fe41c6b8535..b07b22045478d 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 34740ef1b0b8a..622fcd5977d56 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 860c46a6aaca7..a12892036cbf4 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 2aab69671f429..630e7e1e54c35 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index c8bbb58cd83b2..4fa0c00b40575 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 7aa3001fe08eb..ec8ee2c5788ab 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 6fe0b991d6fa1..cd419843b33df 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 11a20aaa28491..9acc91d6f0b5c 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index fb9811aaea206..5ae3455e2cbad 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -377,7 +377,7 @@ "section": "def-common.Filter", "text": "Filter" }, - "[] | undefined; dataTestSubj?: string | undefined; indexPatterns?: ", + "[] | undefined; dataTestSubj?: string | undefined; isLoading?: boolean | undefined; indexPatterns?: ", { "pluginId": "dataViews", "scope": "common", @@ -385,7 +385,7 @@ "section": "def-common.DataView", "text": "DataView" }, - "[] | undefined; isDisabled?: boolean | undefined; isLoading?: boolean | undefined; timeHistory?: ", + "[] | undefined; isDisabled?: boolean | undefined; timeHistory?: ", { "pluginId": "data", "scope": "public", @@ -1396,6 +1396,26 @@ "path": "src/plugins/unified_search/public/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.savedObjectsManagement", + "type": "Object", + "tags": [], + "label": "savedObjectsManagement", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + } + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1937,9 +1957,9 @@ "Omit", ", \"options\" | \"onChange\" | \"selectedOptions\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"onChange\" | \"isLoading\" | \"selectedOptions\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"onChange\" | \"selectedOptions\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" + ", \"options\" | \"onChange\" | \"isLoading\" | \"selectedOptions\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" ], "path": "src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx", "deprecated": false, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 64d361f9de3a8..78e4d72b93292 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 134 | 2 | 99 | 20 | +| 135 | 2 | 100 | 20 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 938acbf44f8f1..1c796f2d0c5da 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 134 | 2 | 99 | 20 | +| 135 | 2 | 100 | 20 | ## Client diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index be3bbff9a466a..251c3976efaa3 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 36998816dcddc..187056013faad 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 185f5306ae783..4a50715f06f72 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 2bb4750747ba0..9e20415e34457 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 8d9aea5b9f72e..b36acb49774ff 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index efe160318e302..419596ef273d8 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 42329bc9873f6..085a92a1c350e 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index a4d39e8071188..f0086b7a27760 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index c7663b05e89cc..cdb467ab5639a 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 62b8b9db10bc0..7dab9f090a3f4 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 2cccc99f502f2..4ac80c6282e5c 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 813c2d581da8f..0839b6994e220 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 7530cbec77b3f..f4b497e28b586 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 638333102c3b5..20ff6c3168e02 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-02-28 +date: 2023-03-06 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/dev_docs/tutorials/versioning_interfaces.mdx b/dev_docs/tutorials/versioning_interfaces.mdx new file mode 100644 index 0000000000000..d8ffdb333294a --- /dev/null +++ b/dev_docs/tutorials/versioning_interfaces.mdx @@ -0,0 +1,442 @@ +--- +id: kibDevTutorialVersioningInterfaces +slug: /kibana-dev-docs/versioning-interfaces +title: Versioning interfaces +description: We need to keep old versions of interfaces available. This tutorial describes a strategy for managing versions of your interfaces over time. +date: 2023-02-09 +tags: ['kibana', 'onboarding', 'dev', 'architecture'] +--- + +To support versioned APIs we need to keep past versions of interfaces around. This tutorial presents one strategy to manage your interfaces. + +## The strategy + +Every plugin has a _domain_. A domain consists of one or more concepts, usually objects, that are related in some logical way. + +At a high level the strategy for versioning our interfaces is: + +> Version a collection of related interfaces together. Whenever a single interface changes, increment the version of the entire collection of interfaces. + +Characteristics of this strategy: + +1. Avoid the `extends` keyword for referencing past interfaces. This results in cleaner API docs being generated. +2. Leverage `* as` to create versioned namespaces rather than versioned interfaces. +3. Verbosity is intentional: for a single change in a collection, we create a new version of the related collection. + +### An example: Weather insights unversioned +Consider a fictional Kibana plugin called "Weather insights". It allows users to easily create and curate dashboards that house visualisations and metrics for weather data. The domain contains the following: + +1. **Data source**: an endpoint that is scraped and ingested into ES for aggregation and search. +2. **Weather dashboard**: a set of visualisations and metrics that draws from a data source. + +We have the following `common` interfaces: + +```ts +export interface DataSource { + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; +} + +/** Fictional type, only used for this example as an external type */ +import type { PortableDashboard } from '@kbn/dashboard/common'; + +export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** Data source to use with this dashboard */ + source: DataSource; +} + +type UnsavedWeatherDashboard = Omit; + +/** Below are the HTTP APIs based on the domain types */ + +export interface GetWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPBody { + weatherDashboard: UnsavedWeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +// Imagine this pattern for the update and list endpoints too. +``` + +These interfaces might be in use by an unversioned HTTP route such as: + +```ts +import type { GetWeatherDashboardHTTPResponse } from '../common'; + +const handler = (ctx, req, res) => { + const body: GetWeatherDashboardHTTPResponse = { ... }; + return res.ok({ body }); +} +``` + +### An example: Weather insights versioned + +The following product requirements are added to the Weather insights plugin: + +1. Data sources need a more user-friendly `name`, currently we just show a URL +2. Some users have said they'd like to give data sources a short `description` too +3. New feature: **Predictions** that will generate forecasts based on past and current weather data + +Before we make these changes, we need to make sure that our current interfaces are not lost. + +#### Preparing our interfaces for versions + +We are going to make our current set of interfaces `v1`. Let's reflect this in our code by creating this file structure in `common`: + +``` +common + index.ts + latest.ts + data_source + v1.ts + weather_dashboard + v1.ts +public + ... +server + ... +``` + +By placing `DataSource` and `WeatherDashboard` in two folders we indicate that they _could_ version independently. Specifically, +`WeatherDashboard` may version independently of `DataSource`. + +**Note:** At this stage, splitting these concepts may be overkill if we expect `WeatherDashboard` to _rarely_ change, but for this example we would like the ability to version them separately. + +The `latest.ts` file is going to act as an alias to the latest set of interfaces. It will look like this: + +```ts +// "export *" is considered safe here because we ONLY have types in these files. +export * from './data_source/v1'; +export * from './weather_data/v1'; +``` + +Our `index.ts` file will contain: + +```ts +// Explicit export of everything from latest +export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse } from './latest'; + +export * as weatherDashboardV1 from './weather_dashboard/v1'; +export * as dataSourceV1 from './data_source/v1'; +``` + +How we use these types depends on whether the code is aware of versions or not. Generally, code that is aware of multiple versions will lead to greater complexity. We might have a logger function that expects the latest dashboards: + +```ts +// This type is being pulled from "latest", so whenever a new latest is linked, +// it will also point to the latest WeatherDashboard. +import type { WeatherDashboard } from '../common'; + +function logIt(dashboard: WeatherDashboard) { + logger.debug(dashboard); +} +``` + +Or we might have a route handler that expects a specific version our domain objects: + +```ts +// Now we bring it all together by using our versioned types in our handler from before: +import type { weatherDashboardV1 } from '../common'; + +const handler = (ctx, req, res) => { + const body: weatherDashboardV1.GetWeatherDashboardHTTPResponse = { ... }; + return res.ok({ body }); +} +``` + + + By referencing types linked from `latest.ts` we will not have to refactor all our app code to point to the latest version of `WeatherDashboard`. This happens by simply re-exporting the newest types from `latest.ts`. Otherwise, code that needs to know about a specific version or versions will have access to it by using the appropriate namespace. + + +### Introducing a new field to `DataSource` + +Now that we have prepared our interfaces to be versioned, let's introduce the `name` field to `DataSource`. + +Create a new `v2.ts` file under the `data_source` directory: + +``` +common + data_source + v1.ts + v2.ts +``` + +The `v2.ts` file will contain: + +```ts +export interface DataSource { + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** A user-friendly name */ + name: string; + /** A longer description of this data source */ + description?: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; +} +``` + + + If we had only added the optional `description` field to `DataSource` this would be a backwards-compatible change that does not require a new version. We could have remained on `v1` of the `DataSource` interface. + + +We also need to update the `WeatherDashboard` interface to use our new `DataSource` interface: + +``` +common + weather_dashboard + v1.ts + v2.ts +``` + +```ts +// common/weather_dashboard/v2.ts + +// This is largely a copy of v1.ts, but using a new WeatherDashboard type in all +// the relevant places + +/** Fictional type, only used for this example as an external type */ +import type { PortableDashboard } from '@kbn/dashboard/common'; +import type { DataSource } from '../data_source/v2'; + +// Optionally, re-export the entire set of types. Interfaces and types declared after this will override v1 declarations. +export * from './v1'; + +export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** Data source to use with this dashboard */ + source: DataSource; +} + +type UnsavedWeatherDashboard = Omit; + +/** Below are the HTTP APIs based on the domain types */ + +export interface GetWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPBody { + weatherDashboard: UnsavedWeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +// Imagine this pattern for the update and list endpoints too. + +``` + + + It is possible to add `export * from './v1';` to the top of our `weather_dashboard/v2.ts` file to re-export unchanged types and avoid copying the whole file. + + However, copying and adapting the entire set of types is OK too. If we have correctly grouped our sub-domains we should be versioning all collections of types together. + + +Now we update the `latest.ts` and `index.ts` files accordingly: + +```ts +// latest.ts +export * from './data_source/v2'; +export * from './weather_dashboard/v2'; +``` + +```ts +// index.ts +// Explicit export of everything from latest +export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse } from './latest'; + +export * as weatherDashboardV1 from './weather_dashboard/v1'; +export * as dataSourceV1 from './data_source/v1'; + +export * as weatherDashboardV2 from './weather_dashboard/v2'; +export * as dataSourceV2 from './data_source/v2'; +``` + +### Introduce a new sub-domain: `Prediction` + +For our second product requirement we are going to introduce a new concept. Let's start by updating our folder structure: + +``` +common + index.ts + latest.ts + data_source + weather_dashboard + prediction + v1.ts +``` + +```ts +// common/prediction/v1.ts + +import type { DataSource } from '../data_source/v2'; + +/** Random set of weather models, just for illustration */ +type Model = 'GFS' | 'ECMWF' | 'GEM'; + +/** Limited set of options rather than "string" so we can more easily version */ +type TimeFrame = '1d' | '2d' | '3d' | '4d' | '5d' | '6d' | '1w' | '2w'; + +export interface PredictionInput { + /** How far back we should look in our data */ + start_date: string; // !! Introduction of inconsistent snake case naming convention, will need a new version to fix + /** How far out to predict. Note, longer time frames result in lower accuracy */ + timeFrame: TimeFrame; + /** Choice of weather model to use */ + model: Model; + /** Data source for this prediction */ + dataSource: DataSource; +} + +export interface PredictionOutput { ... } + +// Below are HTTP APIs for predictions + +export interface CreatePredictionHTTPBody { + input: PredictionInput; +} + +export interface CreatePredictionHTTPResponse { + result: PredictionOutput; +} + +// And so forth... +``` + + + Both `WeatherDashboard` and `Prediction` reference `DataSource`. Therefore, both will increment their version whenever `DataSource` changes. + + +## Challenge + +Normalize our domain objects by only holding a reference to `DataSource` in `WeatherDashboard` and `Prediction` domain objects. + +
+ Solution +
+ 1. We will add a way to uniquely identify a `DataSource` + 2. `DataSource` is currently specified in both `WeatherDashboard` and `Prediction`, therefore both will need to be updated as well. + + Let's start by updating our folder structure to introduce the new versions. + + ``` + common + data_source + v1.ts + v2.ts + v3.ts + weather_dashboard + v1.ts + v2.ts + v3.ts + prediction + v1.ts + v2.ts + ``` + + Now, let's add our new types : + + ```ts + // common/data_source/v3.ts + export interface DataSource { + /** A string that uniquely identifies a DataSource */ + id: string; + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** A user-friendly name */ + name: string; + /** A longer description of this data source */ + description?: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; + } + // ...followed by all other HTTP related interfaces + + // common/weather_dashboard/v3.ts + export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** ID of the data source to use with this dashboard */ + dataSourceId: string; + } + // ...followed by all other HTTP related interfaces + + // common/prediction/v3.ts + export interface PredictionInput { + /** How far back we should look in our data */ + startDate: string; // Fixed inconsistent naming + /** How far out to predict. Note, longer time frames result in lower accuracy */ + timeFrame: TimeFrame; + /** Choice of weather model to use */ + model: Model; + /** Data source for this prediction */ + dataSourceId: string; + } + // ...followed by all other HTTP related interfaces + ``` + + Next let's update `latest.ts` and `index.ts`: + + ```ts + // latest.ts + export * from './data_source/v3'; + export * from './weather_dashboard/v3'; + export * from './prediction/v3'; + + // index.ts + export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse, Prediction } from './latest'; + export * as weatherDashboardV1 from './weather_dashboard/v1'; + export * as dataSourceV1 from './data_source/v1'; + export * as predictionV1 from './prediction/v1'; + + export * as weatherDashboardV2 from './weather_dashboard/v2'; + export * as dataSourceV2 from './data_source/v2'; + export * as predictionV2 from './prediction/v2'; + + export * as weatherDashboardV3 from './weather_dashboard/v3'; + export * as dataSourceV3 from './data_source/v3'; + ``` + + Refactoring interfaces in this way creates a much larger maintenance burden because: + + 1. application code must now translate between the denormalized and normalized versions of these interfaces + 2. code must remember to support both old and new naming conventions + + ...for as long as these APIs are in use. We will not cover deprecation strategies in this tutorial (incoming). Sufficed to + say: _take care when designing public APIs_. +
+ +## Additional resources + +* [PR to prepare Saved Objects Management](https://github.com/elastic/kibana/pull/149495) for versioning \ No newline at end of file diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc index d6d5b0d589ac1..1d22d5cd5a906 100644 --- a/docs/api-generated/cases/case-apis-passthru.asciidoc +++ b/docs/api-generated/cases/case-apis-passthru.asciidoc @@ -24,17 +24,17 @@ Any modifications made to this file will be overwritten.
  • delete /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
  • delete /s/{spaceId}/api/cases/{caseId}/comments
  • get /s/{spaceId}/api/cases/{caseId}/user_actions/_find
  • +
  • get /s/{spaceId}/api/cases/configure/connectors/_find
  • +
  • get /s/{spaceId}/api/cases/_find
  • get /s/{spaceId}/api/cases/{caseId}/comments
  • get /s/{spaceId}/api/cases/{caseId}
  • get /s/{spaceId}/api/cases/{caseId}/user_actions
  • get /s/{spaceId}/api/cases/{caseId}/alerts
  • get /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
  • get /s/{spaceId}/api/cases/configure
  • -
  • get /s/{spaceId}/api/cases/configure/connectors/_find
  • get /s/{spaceId}/api/cases/reporters
  • get /s/{spaceId}/api/cases/status
  • get /s/{spaceId}/api/cases/tags
  • -
  • get /s/{spaceId}/api/cases/_find
  • get /s/{spaceId}/api/cases/alerts/{alertId}
  • post /s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push
  • post /s/{spaceId}/api/cases/configure
  • @@ -530,12 +530,270 @@ Any modifications made to this file will be overwritten. 4xx_response
    +
    +
    + Up +
    get /s/{spaceId}/api/cases/configure/connectors/_find
    +
    Retrieves information about connectors. (findCaseConnectors)
    +
    In particular, only the connectors that are supported for use in cases are returned. You must have read privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges.
    + +

    Path parameters

    +
    +
    spaceId (required)
    + +
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    +
    + + + + + + +

    Return type

    + + + + +

    Example data

    +
    Content-Type: application/json
    +
    {
    +  "isPreconfigured" : true,
    +  "isDeprecated" : true,
    +  "actionTypeId" : ".none",
    +  "referencedByCount" : 0,
    +  "name" : "name",
    +  "id" : "id",
    +  "config" : {
    +    "projectKey" : "projectKey",
    +    "apiUrl" : "apiUrl"
    +  },
    +  "isMissingSecrets" : true
    +}
    + +

    Produces

    + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
      +
    • application/json
    • +
    + +

    Responses

    +

    200

    + Indicates a successful call. + +

    401

    + Authorization information is missing or invalid. + 4xx_response +
    +
    +
    +
    + Up +
    get /s/{spaceId}/api/cases/_find
    +
    Retrieves a paginated subset of cases. (findCases)
    +
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    + +

    Path parameters

    +
    +
    spaceId (required)
    + +
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    +
    + + + + +

    Query parameters

    +
    +
    assignees (optional)
    + +
    Query Parameter — Filters the returned cases by assignees. Valid values are none or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
    defaultSearchOperator (optional)
    + +
    Query Parameter — The default operator to use for the simple_query_string. default: OR
    fields (optional)
    + +
    Query Parameter — The fields in the entity to return in the response. default: null
    from (optional)
    + +
    Query Parameter — [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    owner (optional)
    + +
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    page (optional)
    + +
    Query Parameter — The page number to return. default: 1
    perPage (optional)
    + +
    Query Parameter — The number of cases to return per page. default: 20
    reporters (optional)
    + +
    Query Parameter — Filters the returned cases by the user name of the reporter. default: null
    search (optional)
    + +
    Query Parameter — An Elasticsearch simple_query_string query that filters the objects in the response. default: null
    searchFields (optional)
    + +
    Query Parameter — The fields to perform the simple_query_string parsed query against. default: null
    severity (optional)
    + +
    Query Parameter — The severity of the case. default: null
    sortField (optional)
    + +
    Query Parameter — Determines which field is used to sort the results. default: createdAt
    sortOrder (optional)
    + +
    Query Parameter — Determines the sort order. default: desc
    status (optional)
    + +
    Query Parameter — Filters the returned cases by state. default: null
    tags (optional)
    + +
    Query Parameter — Filters the returned cases by tags. default: null
    to (optional)
    + +
    Query Parameter — [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    +
    + + +

    Return type

    + + + + +

    Example data

    +
    Content-Type: application/json
    +
    {
    +  "count_in_progress_cases" : 6,
    +  "per_page" : 5,
    +  "total" : 2,
    +  "cases" : [ {
    +    "owner" : "cases",
    +    "totalComment" : 0,
    +    "settings" : {
    +      "syncAlerts" : true
    +    },
    +    "totalAlerts" : 0,
    +    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    +    "comments" : [ null, null ],
    +    "assignees" : [ {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    }, {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    } ],
    +    "created_at" : "2022-05-13T09:16:17.416Z",
    +    "description" : "A case description.",
    +    "title" : "Case title 1",
    +    "created_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "version" : "WzUzMiwxXQ==",
    +    "closed_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "tags" : [ "tag-1" ],
    +    "duration" : 120,
    +    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    +    "updated_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    +    "external_service" : {
    +      "external_title" : "external_title",
    +      "pushed_by" : {
    +        "full_name" : "full_name",
    +        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +        "email" : "email",
    +        "username" : "elastic"
    +      },
    +      "external_url" : "external_url",
    +      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    +      "connector_id" : "connector_id",
    +      "external_id" : "external_id",
    +      "connector_name" : "connector_name"
    +    }
    +  }, {
    +    "owner" : "cases",
    +    "totalComment" : 0,
    +    "settings" : {
    +      "syncAlerts" : true
    +    },
    +    "totalAlerts" : 0,
    +    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    +    "comments" : [ null, null ],
    +    "assignees" : [ {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    }, {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    } ],
    +    "created_at" : "2022-05-13T09:16:17.416Z",
    +    "description" : "A case description.",
    +    "title" : "Case title 1",
    +    "created_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "version" : "WzUzMiwxXQ==",
    +    "closed_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "tags" : [ "tag-1" ],
    +    "duration" : 120,
    +    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    +    "updated_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    +    "external_service" : {
    +      "external_title" : "external_title",
    +      "pushed_by" : {
    +        "full_name" : "full_name",
    +        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +        "email" : "email",
    +        "username" : "elastic"
    +      },
    +      "external_url" : "external_url",
    +      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    +      "connector_id" : "connector_id",
    +      "external_id" : "external_id",
    +      "connector_name" : "connector_name"
    +    }
    +  } ],
    +  "count_open_cases" : 1,
    +  "count_closed_cases" : 0,
    +  "page" : 5
    +}
    + +

    Produces

    + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
      +
    • application/json
    • +
    + +

    Responses

    +

    200

    + Indicates a successful call. + findCases_200_response +

    401

    + Authorization information is missing or invalid. + 4xx_response +
    +
    Up
    get /s/{spaceId}/api/cases/{caseId}/comments
    Retrieves all the comments from a case. (getAllCaseComments)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.

    Path parameters

    @@ -656,7 +914,7 @@ Any modifications made to this file will be overwritten.
    includeComments (optional)
    -
    Query Parameter — Determines whether case comments are returned. default: true
    +
    Query Parameter — Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. default: true
    @@ -747,7 +1005,7 @@ Any modifications made to this file will be overwritten. Up
    get /s/{spaceId}/api/cases/{caseId}/user_actions
    Returns all user activity for a case. (getCaseActivity)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.

    Path parameters

    @@ -997,65 +1255,6 @@ Any modifications made to this file will be overwritten. 4xx_response

    -
    -
    - Up -
    get /s/{spaceId}/api/cases/configure/connectors/_find
    -
    Retrieves information about connectors. (getCaseConnectors)
    -
    In particular, only the connectors that are supported for use in cases are returned. You must have read privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges.
    - -

    Path parameters

    -
    -
    spaceId (required)
    - -
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    -
    - - - - - - -

    Return type

    - - - - -

    Example data

    -
    Content-Type: application/json
    -
    {
    -  "isPreconfigured" : true,
    -  "isDeprecated" : true,
    -  "actionTypeId" : ".none",
    -  "referencedByCount" : 0,
    -  "name" : "name",
    -  "id" : "id",
    -  "config" : {
    -    "projectKey" : "projectKey",
    -    "apiUrl" : "apiUrl"
    -  },
    -  "isMissingSecrets" : true
    -}
    - -

    Produces

    - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
      -
    • application/json
    • -
    - -

    Responses

    -

    200

    - Indicates a successful call. - -

    401

    - Authorization information is missing or invalid. - 4xx_response -
    -
    Up @@ -1117,66 +1316,9 @@ Any modifications made to this file will be overwritten.
    Up -
    get /s/{spaceId}/api/cases/status
    -
    Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    - -

    Path parameters

    -
    -
    spaceId (required)
    - -
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    -
    - - - - -

    Query parameters

    -
    -
    owner (optional)
    - -
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    -
    - - -

    Return type

    - - - - -

    Example data

    -
    Content-Type: application/json
    -
    {
    -  "count_in_progress_cases" : 6,
    -  "count_open_cases" : 1,
    -  "count_closed_cases" : 0
    -}
    - -

    Produces

    - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
      -
    • application/json
    • -
    - -

    Responses

    -

    200

    - Indicates a successful call. - getCaseStatus_200_response -

    401

    - Authorization information is missing or invalid. - 4xx_response -
    -
    -
    -
    - Up -
    get /s/{spaceId}/api/cases/tags
    -
    Aggregates and returns a list of case tags. (getCaseTags)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    +
    get /s/{spaceId}/api/cases/status
    +
    Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.

    Path parameters

    @@ -1192,21 +1334,25 @@ Any modifications made to this file will be overwritten.
    owner (optional)
    -
    Query Parameter — A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. default: null
    +
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null

    Return type

    + getCaseStatus_200_response - array[String]

    Example data

    Content-Type: application/json
    -
    ""
    +
    {
    +  "count_in_progress_cases" : 6,
    +  "count_open_cases" : 1,
    +  "count_closed_cases" : 0
    +}

    Produces

    This API call produces the following media types according to the Accept request header; @@ -1218,18 +1364,18 @@ Any modifications made to this file will be overwritten.

    Responses

    200

    Indicates a successful call. - + getCaseStatus_200_response

    401

    Authorization information is missing or invalid. 4xx_response

    -
    +
    Up -
    get /s/{spaceId}/api/cases/_find
    -
    Retrieves a paginated subset of cases. (getCases)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    +
    get /s/{spaceId}/api/cases/tags
    +
    Aggregates and returns a list of case tags. (getCaseTags)
    +
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.

    Path parameters

    @@ -1243,169 +1389,23 @@ Any modifications made to this file will be overwritten.

    Query parameters

    -
    assignees (optional)
    - -
    Query Parameter — Filters the returned cases by assignees. Valid values are none or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
    defaultSearchOperator (optional)
    - -
    Query Parameter — The default operator to use for the simple_query_string. default: OR
    fields (optional)
    - -
    Query Parameter — The fields in the entity to return in the response. default: null
    from (optional)
    - -
    Query Parameter — [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    owner (optional)
    - -
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    page (optional)
    - -
    Query Parameter — The page number to return. default: 1
    perPage (optional)
    - -
    Query Parameter — The number of cases to return per page. default: 20
    reporters (optional)
    - -
    Query Parameter — Filters the returned cases by the user name of the reporter. default: null
    search (optional)
    - -
    Query Parameter — An Elasticsearch simple_query_string query that filters the objects in the response. default: null
    searchFields (optional)
    - -
    Query Parameter — The fields to perform the simple_query_string parsed query against. default: null
    severity (optional)
    - -
    Query Parameter — The severity of the case. default: null
    sortField (optional)
    - -
    Query Parameter — Determines which field is used to sort the results. default: createdAt
    sortOrder (optional)
    - -
    Query Parameter — Determines the sort order. default: desc
    status (optional)
    - -
    Query Parameter — Filters the returned cases by state. default: null
    tags (optional)
    - -
    Query Parameter — Filters the returned cases by tags. default: null
    to (optional)
    +
    owner (optional)
    -
    Query Parameter — [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    +
    Query Parameter — A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. default: null

    Return type

    - getCases_200_response + array[String]

    Example data

    Content-Type: application/json
    -
    {
    -  "count_in_progress_cases" : 6,
    -  "per_page" : 5,
    -  "total" : 2,
    -  "cases" : [ {
    -    "owner" : "cases",
    -    "totalComment" : 0,
    -    "settings" : {
    -      "syncAlerts" : true
    -    },
    -    "totalAlerts" : 0,
    -    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    -    "comments" : [ null, null ],
    -    "assignees" : [ {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    }, {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    } ],
    -    "created_at" : "2022-05-13T09:16:17.416Z",
    -    "description" : "A case description.",
    -    "title" : "Case title 1",
    -    "created_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "version" : "WzUzMiwxXQ==",
    -    "closed_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "tags" : [ "tag-1" ],
    -    "duration" : 120,
    -    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    -    "updated_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    -    "external_service" : {
    -      "external_title" : "external_title",
    -      "pushed_by" : {
    -        "full_name" : "full_name",
    -        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -        "email" : "email",
    -        "username" : "elastic"
    -      },
    -      "external_url" : "external_url",
    -      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    -      "connector_id" : "connector_id",
    -      "external_id" : "external_id",
    -      "connector_name" : "connector_name"
    -    }
    -  }, {
    -    "owner" : "cases",
    -    "totalComment" : 0,
    -    "settings" : {
    -      "syncAlerts" : true
    -    },
    -    "totalAlerts" : 0,
    -    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    -    "comments" : [ null, null ],
    -    "assignees" : [ {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    }, {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    } ],
    -    "created_at" : "2022-05-13T09:16:17.416Z",
    -    "description" : "A case description.",
    -    "title" : "Case title 1",
    -    "created_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "version" : "WzUzMiwxXQ==",
    -    "closed_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "tags" : [ "tag-1" ],
    -    "duration" : 120,
    -    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    -    "updated_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    -    "external_service" : {
    -      "external_title" : "external_title",
    -      "pushed_by" : {
    -        "full_name" : "full_name",
    -        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -        "email" : "email",
    -        "username" : "elastic"
    -      },
    -      "external_url" : "external_url",
    -      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    -      "connector_id" : "connector_id",
    -      "external_id" : "external_id",
    -      "connector_name" : "connector_name"
    -    }
    -  } ],
    -  "count_open_cases" : 1,
    -  "count_closed_cases" : 0,
    -  "page" : 5
    -}
    +
    ""

    Produces

    This API call produces the following media types according to the Accept request header; @@ -1417,7 +1417,7 @@ Any modifications made to this file will be overwritten.

    Responses

    200

    Indicates a successful call. - getCases_200_response +

    401

    Authorization information is missing or invalid. 4xx_response @@ -2094,19 +2094,19 @@ Any modifications made to this file will be overwritten.
  • create_case_request_connector -
  • external_service -
  • findCaseActivity_200_response -
  • +
  • findCaseConnectors_200_response_inner -
  • +
  • findCaseConnectors_200_response_inner_config -
  • +
  • findCases_200_response -
  • +
  • findCases_assignees_parameter -
  • +
  • findCases_owner_parameter -
  • getCaseComment_200_response -
  • getCaseConfiguration_200_response_inner -
  • getCaseConfiguration_200_response_inner_connector -
  • getCaseConfiguration_200_response_inner_created_by -
  • getCaseConfiguration_200_response_inner_mappings_inner -
  • getCaseConfiguration_200_response_inner_updated_by -
  • -
  • getCaseConnectors_200_response_inner -
  • -
  • getCaseConnectors_200_response_inner_config -
  • getCaseStatus_200_response -
  • getCasesByAlert_200_response_inner -
  • -
  • getCases_200_response -
  • -
  • getCases_assignees_parameter -
  • -
  • getCases_owner_parameter -
  • owners -
  • payload_alert_comment -
  • payload_alert_comment_comment -
  • @@ -2557,6 +2557,53 @@ Any modifications made to this file will be overwritten.
    userActions (optional)
    +
    +

    findCaseConnectors_200_response_inner - Up

    +
    +
    +
    actionTypeId (optional)
    +
    config (optional)
    +
    id (optional)
    +
    isDeprecated (optional)
    +
    isMissingSecrets (optional)
    +
    isPreconfigured (optional)
    +
    name (optional)
    +
    referencedByCount (optional)
    +
    +
    +
    +

    findCaseConnectors_200_response_inner_config - Up

    +
    +
    +
    apiUrl (optional)
    +
    projectKey (optional)
    +
    +
    +
    +

    findCases_200_response - Up

    +
    +
    +
    cases (optional)
    +
    count_closed_cases (optional)
    +
    count_in_progress_cases (optional)
    +
    count_open_cases (optional)
    +
    page (optional)
    +
    per_page (optional)
    +
    total (optional)
    +
    +
    + +

    getCaseComment_200_response - Up

    @@ -2635,28 +2682,6 @@ Any modifications made to this file will be overwritten.
    profile_uid (optional)
    -
    -

    getCaseConnectors_200_response_inner - Up

    -
    -
    -
    actionTypeId (optional)
    -
    config (optional)
    -
    id (optional)
    -
    isDeprecated (optional)
    -
    isMissingSecrets (optional)
    -
    isPreconfigured (optional)
    -
    name (optional)
    -
    referencedByCount (optional)
    -
    -
    -
    -

    getCaseConnectors_200_response_inner_config - Up

    -
    -
    -
    apiUrl (optional)
    -
    projectKey (optional)
    -
    -

    getCaseStatus_200_response - Up

    @@ -2674,31 +2699,6 @@ Any modifications made to this file will be overwritten.
    title (optional)
    String The case title.
    -
    -

    getCases_200_response - Up

    -
    -
    -
    cases (optional)
    -
    count_closed_cases (optional)
    -
    count_in_progress_cases (optional)
    -
    count_open_cases (optional)
    -
    page (optional)
    -
    per_page (optional)
    -
    total (optional)
    -
    -
    - -

    owners - Up

    The application that owns the cases: Stack Management, Observability, or Elastic Security.
    diff --git a/docs/api/cases.asciidoc b/docs/api/cases.asciidoc index 9ffe69997f714..4caef82f3207b 100644 --- a/docs/api/cases.asciidoc +++ b/docs/api/cases.asciidoc @@ -8,6 +8,7 @@ these APIs: * <> * <> * <> +* <> * <> * <> * <> @@ -33,6 +34,7 @@ include::cases/cases-api-create.asciidoc[leveloffset=+1] include::cases/cases-api-delete-cases.asciidoc[leveloffset=+1] include::cases/cases-api-delete-comments.asciidoc[leveloffset=+1] //FIND +include::cases/cases-api-find-case-activity.asciidoc[leveloffset=+1] include::cases/cases-api-find-cases.asciidoc[leveloffset=+1] include::cases/cases-api-find-connectors.asciidoc[leveloffset=+1] //GET diff --git a/docs/api/cases/cases-api-find-case-activity.asciidoc b/docs/api/cases/cases-api-find-case-activity.asciidoc new file mode 100644 index 0000000000000..e59540c654e28 --- /dev/null +++ b/docs/api/cases/cases-api-find-case-activity.asciidoc @@ -0,0 +1,20 @@ +[[cases-api-find-case-activity]] +== Find case activity API +++++ +Find case activity +++++ + +Finds user activity for a case. + +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + +=== {api-request-title} + +`GET :/api/cases//user_actions/_find` + +`GET :/s//api/cases//user_actions/_find` + diff --git a/docs/api/cases/cases-api-get-case-activity.asciidoc b/docs/api/cases/cases-api-get-case-activity.asciidoc index da23e845164db..db5835709a6ab 100644 --- a/docs/api/cases/cases-api-get-case-activity.asciidoc +++ b/docs/api/cases/cases-api-get-case-activity.asciidoc @@ -6,7 +6,7 @@ Returns all user activity for a case. -deprecated::[8.1.0] +deprecated::[8.1.0,Use <> instead.] [NOTE] ==== diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 3cdca29fcb49d..f420afb81624d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -535,7 +535,7 @@ activities. |{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[fleet] -|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) +|Fleet needs to have Elasticsearch API keys enabled. |{kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] diff --git a/docs/user/alerting/images/rule-types-index-threshold-conditions.png b/docs/user/alerting/images/rule-types-index-threshold-conditions.png index 062b0a426b5d8..9aac7bd26c3c0 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-conditions.png and b/docs/user/alerting/images/rule-types-index-threshold-conditions.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-action.png b/docs/user/alerting/images/rule-types-index-threshold-example-action.png new file mode 100644 index 0000000000000..3544514574891 Binary files /dev/null and b/docs/user/alerting/images/rule-types-index-threshold-example-action.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png b/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png index a43c4bf1f0d37..68b21a62d8a9b 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png and b/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png b/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png new file mode 100644 index 0000000000000..f183a3744db92 Binary files /dev/null and b/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png b/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png deleted file mode 100644 index 5e7c65e1247d8..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png b/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png index 9f4c2ccbec3c0..aaed0acfbf863 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png and b/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-index.png b/docs/user/alerting/images/rule-types-index-threshold-example-index.png index b2f1c78f7add8..29a9c9520d69e 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-index.png and b/docs/user/alerting/images/rule-types-index-threshold-example-index.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-preview.png b/docs/user/alerting/images/rule-types-index-threshold-example-preview.png index 19ad52c45da1c..a7e656297a3f0 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-preview.png and b/docs/user/alerting/images/rule-types-index-threshold-example-preview.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png b/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png index 9d9262dd96a1e..5a1bc35bd22c9 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png and b/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png b/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png deleted file mode 100644 index e7b13ed6e2cc0..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-window.png b/docs/user/alerting/images/rule-types-index-threshold-example-window.png deleted file mode 100644 index 9b8e9a47ae91e..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-window.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-preview.png b/docs/user/alerting/images/rule-types-index-threshold-preview.png deleted file mode 100644 index 2065cbd117b75..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-preview.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-select.png b/docs/user/alerting/images/rule-types-index-threshold-select.png index aeb9de279b3a1..3a00f5da6aa1a 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-select.png and b/docs/user/alerting/images/rule-types-index-threshold-select.png differ diff --git a/docs/user/alerting/rule-types.asciidoc b/docs/user/alerting/rule-types.asciidoc index 7aab91b27e030..e33dc4214dd8c 100644 --- a/docs/user/alerting/rule-types.asciidoc +++ b/docs/user/alerting/rule-types.asciidoc @@ -86,6 +86,6 @@ Alerts associated with security rules are visible only in the {security-app}; they are not visible in *{stack-manage-app} > {rules-ui}*. ============================================== -include::rule-types/index-threshold.asciidoc[] +include::rule-types/index-threshold.asciidoc[leveloffset=+1] include::rule-types/es-query.asciidoc[leveloffset=+1] include::rule-types/geo-rule-types.asciidoc[] diff --git a/docs/user/alerting/rule-types/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc index 870c53f69b9a1..531d56ef7e697 100644 --- a/docs/user/alerting/rule-types/index-threshold.asciidoc +++ b/docs/user/alerting/rule-types/index-threshold.asciidoc @@ -1,21 +1,16 @@ -[role="xpack"] [[rule-type-index-threshold]] -=== Index threshold +== Index threshold The index threshold rule type runs an {es} query. It aggregates field values from documents, compares them to threshold values, and schedules actions to run when the thresholds are met. [float] -==== Create the rule - -Fill in the name and optional tags, then select *Index Threshold*. - -[float] -==== Define the conditions - -Define properties to detect the condition. +=== Rule conditions [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-conditions.png[Five clauses define the condition to detect] +image::user/alerting/images/rule-types-index-threshold-conditions.png[Defining index threshold rule conditions in {kib}] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +When you create an index threshold rule, you must define the conditions for the rule to detect. For example: Index:: This clause requires an *index or data view* and a *time field* that will be used for the *time window*. When:: This clause specifies how the value to be compared to the threshold is calculated. The value is calculated by aggregating a numeric field a the *time window*. The aggregation options are: `count`, `average`, `sum`, `min`, and `max`. When using `count` the document count is used, and an aggregation field is not necessary. @@ -23,78 +18,97 @@ Over/Grouped Over:: This clause lets you configure whether the aggregation is ap Threshold:: This clause defines a threshold value and a comparison operator (one of `is above`, `is above or equals`, `is below`, `is below or equals`, or `is between`). The result of the aggregation is compared to this threshold. Time window:: This clause determines how far back to search for documents, using the *time field* set in the *index* clause. Generally this value should be to a value higher than the *check every* value, to avoid gaps in detection. -If data is available and all clauses have been defined, a preview chart will render the threshold value and display a line chart showing the value for the last 30 intervals. This can provide an indication of recent values and their proximity to the threshold, and help you tune the clauses. - -[role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-preview.png[Five clauses define the condition to detect] +If data is available and all clauses have been defined, a preview chart will render the threshold value and display a line chart showing the value for the last 30 intervals. This can provide an indication of recent values and their proximity to the threshold, and help you tune the clauses. [float] -==== Add action variables +[[action-variables-index-threshold]] +=== Action variables -<> to run when the rule condition is met. The following variables are specific to the index threshold rule. You can also specify <>. +The following action variables are specific to the index threshold rule. You can also specify <>. -`context.title`:: A preconstructed title for the rule. Example: `rule kibana sites - high egress met threshold`. +`context.conditions`:: A description of the threshold condition. Example: `count greater than 4` +`context.date`:: The date, in ISO format, that the rule met the threshold condition. Example: `2020-01-01T00:00:00.000Z`. +`context.group`:: The name of the action group associated with the threshold condition. Example: `threshold met`. `context.message`:: A preconstructed message for the rule. Example: + `rule 'kibana sites - high egress' is active for group 'threshold met':` + `- Value: 42` + `- Conditions Met: count greater than 4 over 5m` + `- Timestamp: 2020-01-01T00:00:00.000Z` - -`context.group`:: The name of the action group associated with the threshold condition. Example: `threshold met`. -`context.date`:: The date, in ISO format, that the rule met the threshold condition. Example: `2020-01-01T00:00:00.000Z`. +`context.title`:: A preconstructed title for the rule. Example: `rule kibana sites - high egress met threshold`. `context.value`:: The value for the rule that met the threshold condition. -`context.conditions`:: A description of the threshold condition. Example: `count greater than 4` [float] -==== Example +=== Example -In this example, you will use the {kib} <> to set up and tune the conditions on an index threshold rule. For this example, you want to detect when any of the top four sites serve more than 420,000 bytes over a 24 hour period. +In this example, you will use the {kib} <> to set up and tune the conditions on an index threshold rule. For this example, you want to detect when any of the top four sites serve more than 420,000 bytes over a 24 hour period. . Open the main menu, then click *{stack-manage-app} > {rules-ui}*. -. Create a new rule that is checked every four hours and triggers actions when the rule status changes. +. Create a new rule. + +.. Provide a rule name and select the **Index threshold** rule type. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-select.png[Choosing an index threshold rule type] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Select the **Index threshold** rule type. - -. Click *Index*, and set *Indices to query* to *kibana_sample_data_logs*. +.. Select an index. Click *Index*, and set *Indices to query* to `kibana_sample_data_logs`. Set the *Time field* to `@timestamp`. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-index.png[Choosing an index] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Set the *Time field* to *@timestamp*. -+ -[role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-timefield.png[Choosing a time field] - -. To detect the number of bytes served during the time window, click *When* and select `sum` as the aggregation, and bytes as the field to aggregate. +.. To detect the number of bytes served during the time window, click *When* and select `sum` as the aggregation, and `bytes` as the field to aggregate. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-aggregation.png[Choosing the aggregation] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. To detect the four sites that have the most traffic, click *Over* and select `top`, enter `4`, and select `host.keyword` as the field. +.. To detect the four sites that have the most traffic, click *Over* and select `top`, enter `4`, and select `host.keyword` as the field. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-grouping.png[Choosing the groups] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. To trigger the rule when any of the top four sites exceeds 420,000 bytes over a 24 hour period, select `is above` and enter `420000`. +.. Define the condition. To trigger the rule when any of the top four sites exceeds 420,000 bytes over a 24 hour period, select `is above` and enter `420000`. Then click *For the last*, enter `24`, and select `hours`. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-threshold.png[Setting the threshold] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Finally, click *For the last*, enter `24` and select `hours` to complete the rule configuration. +.. Schedule the rule to check every four hours. + +-- [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-window.png[Setting the time window] +image::user/alerting/images/rule-types-index-threshold-example-preview.png[Setting the check interval] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +The preview chart will render showing the 24 hour sum of bytes at 4 hours intervals (the _check interval_) for the past 120 hours (the last 30 intervals). +-- -. The preview chart will render showing the 24 hour sum of bytes at 4 hours intervals (the *check every* interval) for the past 120 hours (the last 30 intervals). +.. Change the time window and observe the effect it has on the chart. Compare a 24 window to a 12 hour window. Notice the variability in the sum of bytes, due to different traffic levels during the day compared to at night. This variability would result in noisy rules, so the 24 hour window is better. The preview chart can help you find the right values for your rule. + +.. Define the actions for your rule. + +-- +You can add one or more actions to your rule to generate notifications when its conditions are met and when they are no longer met. For each action, you must select a connector, set the action frequency, and compose the notification details. +For example, add an action that uses a server log connector to write an entry to the Kibana server log: + [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-preview.png[Setting the time window] +image::user/alerting/images/rule-types-index-threshold-example-action.png[Add an action to the rule] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +NOTE: The index threshold rule does not support alert summaries; therefore they do not appear in the action frequency options. + +The unique action variables that you can use in the notification are listed in <>. For more information, refer to <> and <>. +-- -. Change the time window and observe the effect it has on the chart. Compare a 24 window to a 12 hour window. Notice the variability in the sum of bytes, due to different traffic levels during the day compared to at night. This variability would result in noisy rules, so the 24 hour window is better. The preview chart can help you find the right values for your rule. +.. Save the rule. + +. Find the rule and view its details in *{stack-manage-app} > {rules-ui}*. For example, you can see the status of the rule and its alerts: + [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-comparison.png[Comparing two time windows] \ No newline at end of file +image::user/alerting/images/rule-types-index-threshold-example-alerts.png[View the list of alerts for the rule] + +. Delete or disable this example rule when it's no longer useful. In the detailed rule view, select *Delete rule* from the actions menu. + diff --git a/docs/user/dashboard/create-panels-with-editors.asciidoc b/docs/user/dashboard/create-panels-with-editors.asciidoc index ff61e03b381fe..9b55a86784636 100644 --- a/docs/user/dashboard/create-panels-with-editors.asciidoc +++ b/docs/user/dashboard/create-panels-with-editors.asciidoc @@ -62,7 +62,7 @@ | | Gauge and Goal -| +| ✓ | ✓ | ✓ | ✓ @@ -136,7 +136,7 @@ | ✓ | Synchronized tooltips -| +| ✓ | ✓ | | @@ -196,7 +196,7 @@ | | Annotations -| +| ✓ | ✓ | | @@ -256,11 +256,11 @@ | ✓ | <> -| +| Use <> | ✓ | Static value -| +| ✓ | ✓ |=== @@ -301,7 +301,7 @@ Metric aggregations are calculated from the values in the aggregated documents. | ✓ | Percentiles Rank -| +| ✓ | ✓ | ✓ | ✓ @@ -325,13 +325,13 @@ Metric aggregations are calculated from the values in the aggregated documents. | ✓ | Value count -| +| ✓ | | ✓ | ✓ | Variance -| +| ✓ | ✓ | | ✓ @@ -364,7 +364,7 @@ Bucket aggregations group, or bucket, documents based on the aggregation type. T | Date range | Use filters -| +| ✓ | ✓ | ✓ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png deleted file mode 100644 index 5a6cfb2bf740a..0000000000000 Binary files a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png new file mode 100644 index 0000000000000..64062478abe28 Binary files /dev/null and b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png deleted file mode 100644 index 9c2e3626bc07f..0000000000000 Binary files a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png new file mode 100644 index 0000000000000..19b2d51216491 Binary files /dev/null and b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index ff6ffe230f7e7..eafe5cbf076c3 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -21,7 +21,7 @@ With *Lens*, you can: If you're unsure about the visualization type you want to use, or how you want to display the data, drag the fields you want to visualize onto the workspace, then let *Lens* choose for you. -If you already know the visualization type you want to use, and how you want to display the data, use the following process: +If you already know the visualization type you want to use, and how you want to display the data, use the following process. Choose the visualization type. diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index 0c183b91fc495..18b2b0a148154 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -130,12 +130,12 @@ To save space on the dashboard, hide the axis labels. . Open the *Left axis* menu, then select *None* from the *Axis title* dropdown. + [role="screenshot"] -image::images/lens_lineChartMetricOverTimeLeftAxis_8.3.png[Left axis menu] +image::images/lens_lineChartMetricOverTimeLeftAxis_8.7.png[Left axis menu] . Open the *Bottom axis* menu, then select *None* from the *Axis title* dropdown. + [role="screenshot"] -image::images/lens_lineChartMetricOverTimeBottomAxis_8.3.png[Bottom axis menu] +image::images/lens_lineChartMetricOverTimeBottomAxis_8.7.png[Bottom axis menu] . Click *Save and return* diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index a10c3a6ec8737..e85c46d04ed38 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -19,17 +19,17 @@ You access the options from the *Share* menu in the toolbar. The sharing options * *CSV Reports* — Generate and download a CSV file of a *Discover* saved search. -* *Permalinks* — Share a direct link to a *Discover* saved search, dashboard, or visualization. +* *Get links* — Share a direct link to a *Discover* saved search, dashboard, or visualization. * *Download as JSON* — Generate and download a JSON file of a *Canvas* workpad. * beta[] *Share on a website* — Download and securely share *Canvas* workpads on any website. -* *Embed code* — Embed a fully interactive dashboard or visualization as an iframe on a web page. +* *Embed code* — Embed a fully interactive dashboard as an iframe on a web page. [[reporting-on-cloud-resource-requirements]] -NOTE: For Elastic Cloud deployments, Kibana instances require a minimum of 2GB RAM to generate PDF or PNG reports. To -change Kibana sizing, {ess-console}[edit the deployment]. +NOTE: For Elastic Cloud deployments, {kib} instances require a minimum of 2GB RAM to generate PDF or PNG reports. To +change {kib} sizing, {ess-console}[edit the deployment]. [float] [[manually-generate-reports]] @@ -84,7 +84,7 @@ Share a direct link to a saved search, dashboard, or visualization. To access th . Open the main menu, then open the saved search, dashboard, or visualization you want to share. -. From the toolbar, click *Share*, then select *Permalinks*. +. From the toolbar, click *Share*, then select *Get links*. . Specify how you want to generate the link: @@ -142,11 +142,11 @@ NOTE: Shareable workpads encode the current state of the workpad in a JSON file. [[embed-code]] == Embed code -Display your dashboard or visualization on an internal company website or personal web page with an iframe. Embedding other {kib} objects is generally supported, but you might need to manually craft the proper HTML code. +Display your dashboard on an internal company website or personal web page with an iframe. Embedding other {kib} objects is generally supported, but you might need to manually craft the proper HTML code. Some users might not have access to the dashboard or visualization. For more information, refer to <> and <>. -. Open the main menu, then open the dashboard or visualization you want to share. +. Open the main menu, then open the dashboard you want to share. . Click *Share > Embed code*. @@ -156,7 +156,7 @@ Some users might not have access to the dashboard or visualization. For more inf * To display up-to-date changes, select *Saved object*. -* Select the dashboard or visualization elements you want to include. +* Select the dashboard components you want to include. * To generate a shortened link, select *Short URL*. diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 490718262a816..cfee44522daad 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -128,6 +128,10 @@ { "id": "kibDevTutorialSavedObject" }, + { + "id": "kibDevTutorialVersioningInterfaces", + "label": "Versioning interfaces" + }, { "id": "kibDevTutorialSubmitPullRequest" }, diff --git a/package.json b/package.json index 5d31e6ef818a2..1d172570584c0 100644 --- a/package.json +++ b/package.json @@ -857,7 +857,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.7.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.43.1", + "react-hook-form": "^7.43.2", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -921,7 +921,7 @@ "usng.js": "^0.4.5", "utility-types": "^3.10.0", "uuid": "9.0.0", - "vega": "^5.22.1", + "vega": "^5.23.0", "vega-interpreter": "^1.0.4", "vega-lite": "^5.5.0", "vega-schema-url-parser": "^2.2.0", @@ -943,7 +943,7 @@ "@babel/eslint-plugin": "^7.19.1", "@babel/generator": "^7.21.1", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/parser": "^7.21.1", + "@babel/parser": "^7.21.2", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", @@ -955,8 +955,8 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", "@babel/register": "^7.21.0", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -1449,7 +1449,7 @@ "svgo": "^2.8.0", "tape": "^5.0.1", "tempy": "^0.3.0", - "terser": "^5.16.4", + "terser": "^5.16.5", "terser-webpack-plugin": "^4.2.3", "tough-cookie": "^4.1.2", "tree-kill": "^1.2.2", diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts index 84ae0392ce1d6..9f50cdee84e3f 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts @@ -9,7 +9,7 @@ export { ScopedClusterClient } from './src/scoped_cluster_client'; export { ClusterClient } from './src/cluster_client'; export { configureClient } from './src/configure_client'; -export { type AgentStore, AgentManager, type NetworkAgent } from './src/agent_manager'; +export { type AgentStatsProvider, AgentManager, type NetworkAgent } from './src/agent_manager'; export { getRequestDebugMeta, getErrorMessage } from './src/log_query_and_deprecation'; export { PRODUCT_RESPONSE_HEADER, diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts index 0f374bd8311e5..448bfe2674e8f 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts @@ -6,9 +6,12 @@ * Side Public License, v 1. */ -import { AgentManager } from './agent_manager'; import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent } from 'https'; +import type { ElasticsearchClientsMetrics } from '@kbn/core-metrics-server'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; +import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks'; +import { AgentManager } from './agent_manager'; jest.mock('http'); jest.mock('https'); @@ -17,6 +20,12 @@ const HttpAgentMock = HttpAgent as jest.Mock; const HttpsAgentMock = HttpsAgent as jest.Mock; describe('AgentManager', () => { + let logger: MockedLogger; + + beforeEach(() => { + logger = loggerMock.create(); + }); + afterEach(() => { HttpAgentMock.mockClear(); HttpsAgentMock.mockClear(); @@ -24,7 +33,7 @@ describe('AgentManager', () => { describe('#getAgentFactory()', () => { it('provides factories which are different at each call', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory1 = agentManager.getAgentFactory(); const agentFactory2 = agentManager.getAgentFactory(); expect(agentFactory1).not.toEqual(agentFactory2); @@ -36,7 +45,7 @@ describe('AgentManager', () => { HttpAgentMock.mockImplementationOnce(() => mockedHttpAgent); const mockedHttpsAgent = new HttpsAgent(); HttpsAgentMock.mockImplementationOnce(() => mockedHttpsAgent); - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory = agentManager.getAgentFactory(); const httpAgent = agentFactory({ url: new URL('http://elastic-node-1:9200') }); const httpsAgent = agentFactory({ url: new URL('https://elastic-node-1:9200') }); @@ -45,7 +54,7 @@ describe('AgentManager', () => { }); it('takes into account the provided configurations', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory = agentManager.getAgentFactory({ maxTotalSockets: 1024, scheduling: 'fifo', @@ -68,7 +77,7 @@ describe('AgentManager', () => { }); it('provides Agents that match the URLs protocol', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory = agentManager.getAgentFactory(); agentFactory({ url: new URL('http://elastic-node-1:9200') }); expect(HttpAgent).toHaveBeenCalledTimes(1); @@ -79,7 +88,7 @@ describe('AgentManager', () => { }); it('provides the same Agent if URLs use the same protocol', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory = agentManager.getAgentFactory(); const agent1 = agentFactory({ url: new URL('http://elastic-node-1:9200') }); const agent2 = agentFactory({ url: new URL('http://elastic-node-2:9200') }); @@ -92,7 +101,7 @@ describe('AgentManager', () => { }); it('dereferences an agent instance when the agent is closed', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory = agentManager.getAgentFactory(); const agent = agentFactory({ url: new URL('http://elastic-node-1:9200') }); // eslint-disable-next-line dot-notation @@ -105,7 +114,7 @@ describe('AgentManager', () => { describe('two agent factories', () => { it('never provide the same Agent instance even if they use the same type', () => { - const agentManager = new AgentManager(); + const agentManager = new AgentManager(logger); const agentFactory1 = agentManager.getAgentFactory(); const agentFactory2 = agentManager.getAgentFactory(); const agent1 = agentFactory1({ url: new URL('http://elastic-node-1:9200') }); @@ -115,20 +124,34 @@ describe('AgentManager', () => { }); }); - describe('#getAgents()', () => { - it('returns the created HTTP and HTTPs Agent instances', () => { - const agentManager = new AgentManager(); - const agentFactory1 = agentManager.getAgentFactory(); - const agentFactory2 = agentManager.getAgentFactory(); - const agent1 = agentFactory1({ url: new URL('http://elastic-node-1:9200') }); - const agent2 = agentFactory2({ url: new URL('http://elastic-node-1:9200') }); - const agent3 = agentFactory1({ url: new URL('https://elastic-node-1:9200') }); - const agent4 = agentFactory2({ url: new URL('https://elastic-node-1:9200') }); + describe('#getAgentsStats()', () => { + it('returns the stats of the agents', () => { + const agentManager = new AgentManager(logger); + const metrics: ElasticsearchClientsMetrics = { + totalQueuedRequests: 0, + totalIdleSockets: 100, + totalActiveSockets: 400, + }; + getAgentsSocketsStatsMock.mockReturnValue(metrics); + + expect(agentManager.getAgentsStats()).toStrictEqual(metrics); + }); + + it('warns when there are queued requests (requests unassigned to any socket)', () => { + const agentManager = new AgentManager(logger); + const metrics: ElasticsearchClientsMetrics = { + totalQueuedRequests: 2, + totalIdleSockets: 100, // There may be idle sockets when many clients are initialized. It should not be taken as an indicator of health. + totalActiveSockets: 400, + }; + getAgentsSocketsStatsMock.mockReturnValue(metrics); - const agents = agentManager.getAgents(); + expect(agentManager.getAgentsStats()).toStrictEqual(metrics); - expect(agents.size).toEqual(4); - expect([...agents]).toEqual(expect.arrayContaining([agent1, agent2, agent3, agent4])); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + 'There are 2 queued requests. If this number is constantly high, consider scaling Kibana horizontally or increasing "elasticsearch.maxSockets" in the config.' + ); }); }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts index 9f654216bdce8..3e414728b069a 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts @@ -9,6 +9,9 @@ import { Agent as HttpAgent, type AgentOptions } from 'http'; import { Agent as HttpsAgent } from 'https'; import type { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; +import type { Logger } from '@kbn/logging'; +import type { ElasticsearchClientsMetrics } from '@kbn/core-metrics-server'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; const HTTPS = 'https:'; @@ -19,8 +22,14 @@ export interface AgentFactoryProvider { getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory; } -export interface AgentStore { - getAgents(): Set; +/** + * Exposes the APIs to fetch stats of the existing agents. + */ +export interface AgentStatsProvider { + /** + * Returns the {@link ElasticsearchClientsMetrics}, to understand the load on the Elasticsearch HTTP agents. + */ + getAgentsStats(): ElasticsearchClientsMetrics; } /** @@ -34,10 +43,10 @@ export interface AgentStore { * exposes methods that can modify the underlying pools, effectively impacting the connections of other Clients. * @internal **/ -export class AgentManager implements AgentFactoryProvider, AgentStore { - private agents: Set; +export class AgentManager implements AgentFactoryProvider, AgentStatsProvider { + private readonly agents: Set; - constructor() { + constructor(private readonly logger: Logger) { this.agents = new Set(); } @@ -69,8 +78,16 @@ export class AgentManager implements AgentFactoryProvider, AgentStore { }; } - public getAgents(): Set { - return this.agents; + public getAgentsStats(): ElasticsearchClientsMetrics { + const stats = getAgentsSocketsStats(this.agents); + + if (stats.totalQueuedRequests > 0) { + this.logger.warn( + `There are ${stats.totalQueuedRequests} queued requests. If this number is constantly high, consider scaling Kibana horizontally or increasing "elasticsearch.maxSockets" in the config.` + ); + } + + return stats; } } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts index bcf5011395bf6..b64221b913c62 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts @@ -57,7 +57,7 @@ describe('ClusterClient', () => { logger = loggingSystemMock.createLogger(); internalClient = createClient(); scopedClient = createClient(); - agentFactoryProvider = new AgentManager(); + agentFactoryProvider = new AgentManager(logger); authHeaders = httpServiceMock.createAuthHeaderStorage(); authHeaders.get.mockImplementation(() => ({ diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts index fe511f46278d9..1784fa7a08662 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts @@ -54,7 +54,7 @@ describe('configureClient', () => { config = createFakeConfig(); parseClientOptionsMock.mockReturnValue({}); ClientMock.mockImplementation(() => createFakeClient()); - agentFactoryProvider = new AgentManager(); + agentFactoryProvider = new AgentManager(logger); }); afterEach(() => { diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.test.mocks.ts similarity index 100% rename from packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts rename to packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.test.mocks.ts diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.test.ts similarity index 100% rename from packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts rename to packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.test.ts diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.ts similarity index 95% rename from packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts rename to packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.ts index e513528c07697..5c8b61cafe7be 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/get_agents_sockets_stats.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; import type { ElasticsearchClientsMetrics } from '@kbn/core-metrics-server'; +import type { NetworkAgent } from './agent_manager'; export const getAgentsSocketsStats = (agents: Set): ElasticsearchClientsMetrics => { const nodes = new Set(); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json index d43e5bcbe630d..2288caa55dea8 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json @@ -21,6 +21,7 @@ "@kbn/logging-mocks", "@kbn/core-logging-server-mocks", "@kbn/core-http-server-mocks", + "@kbn/core-metrics-server", ], "exclude": [ "target/**/*", diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts index 0b66d449df013..86c9bbe6394fd 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts @@ -15,4 +15,4 @@ export type { DeeplyMockedApi, ElasticsearchClientMock, } from './src/mocks'; -export { createAgentStoreMock } from './src/agent_manager.mocks'; +export { createAgentStatsProviderMock } from './src/agent_manager.mocks'; diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts index 2fd8812b3aae0..2a43142477142 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { AgentStore, NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; +import type { AgentStatsProvider } from '@kbn/core-elasticsearch-client-server-internal'; -export const createAgentStoreMock = (agents: Set = new Set()): AgentStore => ({ - getAgents: jest.fn(() => agents), +export const createAgentStatsProviderMock = (): jest.Mocked => ({ + getAgentsStats: jest.fn(), }); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts index 68a56ff28bc8d..d04b7dfda2b3e 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts @@ -10,7 +10,7 @@ import type { AgentManager } from '@kbn/core-elasticsearch-client-server-interna export const MockClusterClient = jest.fn(); export const MockAgentManager: jest.MockedClass = jest.fn().mockReturnValue({ - getAgents: jest.fn(), + getAgentsStats: jest.fn(), getAgentFactory: jest.fn(), }); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts index f523cfe8c4a8f..a145da71fab83 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts @@ -201,9 +201,9 @@ describe('#setup', () => { ); }); - it('returns an AgentStore as part of the contract', async () => { + it('returns an AgentStatsProvider as part of the contract', async () => { const setupContract = await elasticsearchService.setup(setupDeps); - expect(typeof setupContract.agentStore.getAgents).toEqual('function'); + expect(typeof setupContract.agentStatsProvider.getAgentsStats).toEqual('function'); }); it('esNodeVersionCompatibility$ only starts polling when subscribed to', async () => { diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts index fddff84293140..c1396daf1aa66 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts @@ -66,7 +66,7 @@ export class ElasticsearchService this.config$ = coreContext.configService .atPath('elasticsearch') .pipe(map((rawConfig) => new ElasticsearchConfig(rawConfig))); - this.agentManager = new AgentManager(); + this.agentManager = new AgentManager(this.log.get('agent-manager')); } public async preboot(): Promise { @@ -120,7 +120,9 @@ export class ElasticsearchService } this.unauthorizedErrorHandler = handler; }, - agentStore: this.agentManager, + agentStatsProvider: { + getAgentsStats: this.agentManager.getAgentsStats.bind(this.agentManager), + }, }; } diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts index b03b86c7bdd1c..c026b93943df5 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts @@ -12,7 +12,7 @@ import type { ElasticsearchServiceStart, ElasticsearchServiceSetup, } from '@kbn/core-elasticsearch-server'; -import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; +import type { AgentStatsProvider } from '@kbn/core-elasticsearch-client-server-internal'; import type { ServiceStatus } from '@kbn/core-status-common'; import type { NodesVersionCompatibility, NodeInfo } from './version_check/ensure_es_version'; import type { ClusterInfo } from './get_cluster_info'; @@ -22,7 +22,7 @@ export type InternalElasticsearchServicePreboot = ElasticsearchServicePreboot; /** @internal */ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup { - agentStore: AgentStore; + agentStatsProvider: AgentStatsProvider; clusterInfo$: Observable; esNodesCompatibility$: Observable; status$: Observable>; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts index 26d81da24318c..4cfb3ecc45c32 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts @@ -13,7 +13,7 @@ import { elasticsearchClientMock, type ClusterClientMock, type CustomClusterClientMock, - createAgentStoreMock, + createAgentStatsProviderMock, } from '@kbn/core-elasticsearch-client-server-mocks'; import type { ElasticsearchClientConfig, @@ -95,7 +95,7 @@ const createInternalSetupContractMock = () => { level: ServiceStatusLevels.available, summary: 'Elasticsearch is available', }), - agentStore: createAgentStoreMock(), + agentStatsProvider: createAgentStatsProviderMock(), }; return internalSetupContract; }; diff --git a/packages/core/http/core-http-router-server-internal/src/request.ts b/packages/core/http/core-http-router-server-internal/src/request.ts index 7e8e927bd4671..26ac377c00bf3 100644 --- a/packages/core/http/core-http-router-server-internal/src/request.ts +++ b/packages/core/http/core-http-router-server-internal/src/request.ts @@ -201,6 +201,7 @@ export class CoreKibanaRequest< xsrfRequired: ((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)?.xsrfRequired ?? true, // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 + access: this.getAccess(request), tags: request.route?.settings?.tags || [], timeout: { payload: payloadTimeout, @@ -222,6 +223,13 @@ export class CoreKibanaRequest< options, }; } + /** infer route access from path if not declared */ + private getAccess(request: RawRequest): 'internal' | 'public' { + return ( + ((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)?.access ?? + (request.path.startsWith('/internal') ? 'internal' : 'public') + ); + } private getAuthRequired(request: RawRequest): boolean | 'optional' { if (isFakeRawRequest(request)) { diff --git a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts index 1a3262fdf1f80..9b2d90e18640b 100644 --- a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts +++ b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts @@ -71,7 +71,7 @@ function createKibanaRequestMock

    ({ routeTags, routeAuthRequired, validation = {}, - kibanaRouteOptions = { xsrfRequired: true }, + kibanaRouteOptions = { xsrfRequired: true, access: 'public' }, kibanaRequestState = { requestId: '123', requestUuid: '123e4567-e89b-12d3-a456-426614174000', diff --git a/packages/core/http/core-http-server-internal/src/http_server.test.ts b/packages/core/http/core-http-server-internal/src/http_server.test.ts index 92fa63c502558..b6a120e06ab8d 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.test.ts @@ -817,6 +817,56 @@ test('allows attaching metadata to attach meta-data tag strings to a route', asy await supertest(innerServer.listener).get('/without-tags').expect(200, { tags: [] }); }); +test('allows declaring route access to flag a route as public or internal', async () => { + const access = 'internal'; + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/with-access', validate: false, options: { access } }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/without-access', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener).get('/with-access').expect(200, { access }); + + await supertest(innerServer.listener).get('/without-access').expect(200, { access: 'public' }); +}); + +test('infers access flag from path if not defined', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/internal/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/random/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/random/internal/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + + router.get({ path: '/api/foo/internal/my-foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener).get('/internal/foo').expect(200, { access: 'internal' }); + + await supertest(innerServer.listener).get('/random/foo').expect(200, { access: 'public' }); + await supertest(innerServer.listener) + .get('/random/internal/foo') + .expect(200, { access: 'public' }); + await supertest(innerServer.listener) + .get('/api/foo/internal/my-foo') + .expect(200, { access: 'public' }); +}); + test('exposes route details of incoming request to a route handler', async () => { const { registerRouter, server: innerServer } = await server.setup(config); @@ -833,6 +883,7 @@ test('exposes route details of incoming request to a route handler', async () => options: { authRequired: true, xsrfRequired: false, + access: 'public', tags: [], timeout: {}, }, @@ -1010,6 +1061,7 @@ test('exposes route details of incoming request to a route handler (POST + paylo options: { authRequired: true, xsrfRequired: true, + access: 'public', tags: [], timeout: { payload: 10000, diff --git a/packages/core/http/core-http-server-internal/src/http_server.ts b/packages/core/http/core-http-server-internal/src/http_server.ts index fb19795d77dce..1ef5be6c67a54 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.ts @@ -524,6 +524,7 @@ export class HttpServer { const kibanaRouteOptions: KibanaRouteOptions = { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), + access: route.options.access ?? (route.path.startsWith('/internal') ? 'internal' : 'public'), }; this.server!.route({ diff --git a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts index 5e182005fd40c..d13bd001bbbb9 100644 --- a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts +++ b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts @@ -167,6 +167,7 @@ describe('xsrf post-auth handler', () => { path: '/some-path', kibanaRouteOptions: { xsrfRequired: false, + access: 'public', }, }); diff --git a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts index 3fe9c8ac727ff..af148413265e8 100644 --- a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts +++ b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts @@ -60,6 +60,7 @@ export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPost }; }; +// TODO: implement header required for accessing internal routes. See https://github.com/elastic/kibana/issues/151940 export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => { const { name: serverName, diff --git a/packages/core/http/core-http-server/src/router/request.ts b/packages/core/http/core-http-server/src/router/request.ts index ef33bec14f841..e0664cb1ea29a 100644 --- a/packages/core/http/core-http-server/src/router/request.ts +++ b/packages/core/http/core-http-server/src/router/request.ts @@ -19,6 +19,7 @@ import type { Headers } from './headers'; */ export interface KibanaRouteOptions extends RouteOptionsApp { xsrfRequired: boolean; + access: 'internal' | 'public'; } /** diff --git a/packages/core/http/core-http-server/src/router/route.ts b/packages/core/http/core-http-server/src/router/route.ts index 78d76bb4ba7b8..e2b11aec08e1a 100644 --- a/packages/core/http/core-http-server/src/router/route.ts +++ b/packages/core/http/core-http-server/src/router/route.ts @@ -120,6 +120,18 @@ export interface RouteConfigOptions { */ xsrfRequired?: Method extends 'get' ? never : boolean; + /** + * Defines intended request origin of the route: + * - public. The route is public, declared stable and intended for external access. + * In the future, may require an incomming request to contain a specified header. + * - internal. The route is internal and intended for internal access only. + * + * If not declared, infers access from route path: + * - access =`internal` for '/internal' route path prefix + * - access = `public` for everything else + */ + access?: 'public' | 'internal'; + /** * Additional metadata tag strings to attach to the route. */ diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts index 49b5a4b71da53..5f75edc17235c 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts @@ -6,13 +6,9 @@ * Side Public License, v 1. */ -import { Agent as HttpAgent } from 'http'; -import { Agent as HttpsAgent } from 'https'; import type { ElasticsearchClientsMetrics } from '@kbn/core-metrics-server'; -import { createAgentStoreMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks'; +import { createAgentStatsProviderMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { ElasticsearchClientsMetricsCollector } from './elasticsearch_client'; -import { getAgentsSocketsStats } from './get_agents_sockets_stats'; jest.mock('@kbn/core-elasticsearch-client-server-internal'); @@ -24,16 +20,12 @@ export const sampleEsClientMetrics: ElasticsearchClientsMetrics = { describe('ElasticsearchClientsMetricsCollector', () => { test('#collect calls getAgentsSocketsStats with the Agents managed by the provided AgentManager', async () => { - const agents = new Set([new HttpAgent(), new HttpsAgent()]); - const agentStore = createAgentStoreMock(agents); - getAgentsSocketsStatsMock.mockReturnValueOnce(sampleEsClientMetrics); + const agentStatsProvider = createAgentStatsProviderMock(); + agentStatsProvider.getAgentsStats.mockReturnValue(sampleEsClientMetrics); - const esClientsMetricsCollector = new ElasticsearchClientsMetricsCollector(agentStore); + const esClientsMetricsCollector = new ElasticsearchClientsMetricsCollector(agentStatsProvider); const metrics = await esClientsMetricsCollector.collect(); - expect(agentStore.getAgents).toHaveBeenCalledTimes(1); - expect(getAgentsSocketsStats).toHaveBeenCalledTimes(1); - expect(getAgentsSocketsStats).toHaveBeenNthCalledWith(1, agents); expect(metrics).toEqual(sampleEsClientMetrics); }); }); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts index 278fd0218f8c0..bd6a86f23b3ed 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts @@ -7,16 +7,15 @@ */ import type { ElasticsearchClientsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; -import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; -import { getAgentsSocketsStats } from './get_agents_sockets_stats'; +import type { AgentStatsProvider } from '@kbn/core-elasticsearch-client-server-internal'; export class ElasticsearchClientsMetricsCollector implements MetricsCollector { - constructor(private readonly agentStore: AgentStore) {} + constructor(private readonly agentStatsProvider: AgentStatsProvider) {} public async collect(): Promise { - return await getAgentsSocketsStats(this.agentStore.getAgents()); + return await this.agentStatsProvider.getAgentsStats(); } public reset() { diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts index b29572eb37624..e129991a3576d 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts @@ -52,7 +52,7 @@ describe('MetricsService', () => { expect(OpsMetricsCollector).toHaveBeenCalledTimes(1); expect(OpsMetricsCollector).toHaveBeenCalledWith( httpMock.server, - esServiceMock.agentStore, + esServiceMock.agentStatsProvider, expect.objectContaining({ logger: logger.get('metrics') }) ); diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts index 95a9dc09bba57..97a6111e96536 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts @@ -55,10 +55,14 @@ export class MetricsService this.coreContext.configService.atPath(OPS_CONFIG_PATH) ); - this.metricsCollector = new OpsMetricsCollector(http.server, elasticsearchService.agentStore, { - logger: this.logger, - ...config.cGroupOverrides, - }); + this.metricsCollector = new OpsMetricsCollector( + http.server, + elasticsearchService.agentStatsProvider, + { + logger: this.logger, + ...config.cGroupOverrides, + } + ); await this.refreshMetrics(); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts index 7c4682e4c24c0..c1920e56ce879 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts @@ -29,7 +29,7 @@ describe('OpsMetricsCollector', () => { beforeEach(() => { const hapiServer = httpServiceMock.createInternalSetupContract().server; - const agentManager = new AgentManager(); + const agentManager = new AgentManager(loggerMock.create()); collector = new OpsMetricsCollector(hapiServer, agentManager, { logger: loggerMock.create() }); mockOsCollector.collect.mockResolvedValue('osMetrics'); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts index 8a10f4071b11b..2e358fc121bce 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts @@ -8,7 +8,7 @@ import { Server as HapiServer } from '@hapi/hapi'; import type { OpsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; -import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; +import type { AgentStatsProvider } from '@kbn/core-elasticsearch-client-server-internal'; import { ProcessMetricsCollector, OsMetricsCollector, @@ -23,11 +23,15 @@ export class OpsMetricsCollector implements MetricsCollector { private readonly serverCollector: ServerMetricsCollector; private readonly esClientCollector: ElasticsearchClientsMetricsCollector; - constructor(server: HapiServer, agentStore: AgentStore, opsOptions: OpsMetricsCollectorOptions) { + constructor( + server: HapiServer, + agentStatsProvider: AgentStatsProvider, + opsOptions: OpsMetricsCollectorOptions + ) { this.processCollector = new ProcessMetricsCollector(); this.osCollector = new OsMetricsCollector(opsOptions); this.serverCollector = new ServerMetricsCollector(server); - this.esClientCollector = new ElasticsearchClientsMetricsCollector(agentStore); + this.esClientCollector = new ElasticsearchClientsMetricsCollector(agentStatsProvider); } public async collect(): Promise { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts index 9a4a728184388..61856a30cfc10 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts @@ -28,7 +28,7 @@ export { cloneIndex, waitForTask, updateAndPickupMappings, - updateTargetMappingsMeta, + updateMappings, updateAliases, transformDocs, setWriteBlock, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 2593ac7867d1e..7e380d3a7ad17 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { type Either, right } from 'fp-ts/lib/Either'; +import type { Either } from 'fp-ts/lib/Either'; +import { right } from 'fp-ts/lib/Either'; import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; import type { DocumentsTransformFailed } from '../core/migrate_raw_docs'; @@ -37,11 +38,8 @@ export type { CloneIndexResponse, CloneIndexParams } from './clone_index'; export { cloneIndex } from './clone_index'; export type { WaitForIndexStatusParams, IndexNotYellowTimeout } from './wait_for_index_status'; -import { - type IndexNotGreenTimeout, - type IndexNotYellowTimeout, - waitForIndexStatus, -} from './wait_for_index_status'; +import type { IndexNotGreenTimeout, IndexNotYellowTimeout } from './wait_for_index_status'; +import { waitForIndexStatus } from './wait_for_index_status'; export type { WaitForTaskResponse, WaitForTaskCompletionTimeout } from './wait_for_task'; import { waitForTask, WaitForTaskCompletionTimeout } from './wait_for_task'; @@ -85,8 +83,6 @@ export { createIndex } from './create_index'; export { checkTargetMappings } from './check_target_mappings'; -export { updateTargetMappingsMeta } from './update_target_mappings_meta'; - export const noop = async (): Promise> => right('noop' as const); export type { @@ -95,6 +91,8 @@ export type { } from './update_and_pickup_mappings'; export { updateAndPickupMappings } from './update_and_pickup_mappings'; +export { updateMappings } from './update_mappings'; + import type { UnknownDocsFound } from './check_for_unknown_docs'; import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; import { ClusterShardLimitExceeded } from './create_index'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts index c1fd2f7b0b0fb..da243af9a7ebc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts @@ -46,7 +46,7 @@ describe('updateAndPickupMappings', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); - it('updates the _mapping properties but not the _meta information', async () => { + it('calls the indices.putMapping with the mapping properties as well as the _meta information', async () => { const task = updateAndPickupMappings({ client, index: 'new_index', @@ -82,6 +82,13 @@ describe('updateAndPickupMappings', () => { dynamic: false, }, }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 7f89f862ce128..653a90746dea0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -45,13 +45,11 @@ export const updateAndPickupMappings = ({ RetryableEsClientError, 'update_mappings_succeeded' > = () => { - // ._meta property will be updated on a later step - const { _meta, ...mappingsWithoutMeta } = mappings; return client.indices .putMapping({ index, timeout: DEFAULT_TIMEOUT, - ...mappingsWithoutMeta, + ...mappings, }) .then(() => { // Ignore `acknowledged: false`. When the coordinating node accepts diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts new file mode 100644 index 0000000000000..133f07d7460e5 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts @@ -0,0 +1,147 @@ +/* + * 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 Either from 'fp-ts/lib/Either'; +import type { TransportResult } from '@elastic/elasticsearch'; +import { errors as EsErrors } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; +import { updateMappings } from './update_mappings'; +import { DEFAULT_TIMEOUT } from './constants'; + +jest.mock('./catch_retryable_es_client_errors'); + +describe('updateMappings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const createErrorClient = (response: Partial>>) => { + // Create a mock client that returns the desired response + const apiResponse = elasticsearchClientMock.createApiResponse(response); + const error = new EsErrors.ResponseError(apiResponse); + const client = elasticsearchClientMock.createInternalClient( + elasticsearchClientMock.createErrorTransportRequestPromise(error) + ); + + return { client, error }; + }; + + it('resolves left if the mappings are not compatible (aka 400 illegal_argument_exception from ES)', async () => { + const { client } = createErrorClient({ + statusCode: 400, + body: { + error: { + type: 'illegal_argument_exception', + reason: 'mapper [action.actionTypeId] cannot be changed from type [keyword] to [text]', + }, + }, + }); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }, + }); + + const res = await task(); + + expect(Either.isLeft(res)).toEqual(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + + it('calls catchRetryableEsClientErrors when the promise rejects', async () => { + const { client, error: retryableError } = createErrorClient({ + statusCode: 503, + body: { error: { type: 'es_type', reason: 'es_reason' } }, + }); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: {}, + }, + }); + try { + await task(); + } catch (e) { + /** ignore */ + } + + expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); + }); + + it('updates the mapping information of the desired index', async () => { + const client = elasticsearchClientMock.createInternalClient(); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }, + }); + + const res = await task(); + expect(Either.isRight(res)).toBe(true); + expect(client.indices.putMapping).toHaveBeenCalledTimes(1); + expect(client.indices.putMapping).toHaveBeenCalledWith({ + index: 'new_index', + timeout: DEFAULT_TIMEOUT, + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts new file mode 100644 index 0000000000000..4cf57f3ce7a8d --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts @@ -0,0 +1,63 @@ +/* + * 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 Either from 'fp-ts/lib/Either'; +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import { DEFAULT_TIMEOUT } from './constants'; + +/** @internal */ +export interface UpdateMappingsParams { + client: ElasticsearchClient; + index: string; + mappings: IndexMapping; +} + +/** @internal */ +export interface IncompatibleMappingException { + type: 'incompatible_mapping_exception'; +} + +/** + * Updates an index's mappings and runs an pickupUpdatedMappings task so that the mapping + * changes are "picked up". Returns a taskId to track progress. + */ +export const updateMappings = ({ + client, + index, + mappings, +}: UpdateMappingsParams): TaskEither.TaskEither< + RetryableEsClientError | IncompatibleMappingException, + 'update_mappings_succeeded' +> => { + return () => { + return client.indices + .putMapping({ + index, + timeout: DEFAULT_TIMEOUT, + ...mappings, + }) + .then(() => Either.right('update_mappings_succeeded' as const)) + .catch((res) => { + const errorType = res?.body?.error?.type; + // ES throws this exact error when attempting to make incompatible updates to the mappigns + if ( + res?.statusCode === 400 && + (errorType === 'illegal_argument_exception' || + errorType === 'strict_dynamic_mapping_exception' || + errorType === 'mapper_parsing_exception') + ) { + return Either.left({ type: 'incompatible_mapping_exception' }); + } + return catchRetryableEsClientErrors(res); + }); + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts deleted file mode 100644 index 9116d5389f2ec..0000000000000 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts +++ /dev/null @@ -1,80 +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 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 { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; -import { errors as EsErrors } from '@elastic/elasticsearch'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { updateTargetMappingsMeta } from './update_target_mappings_meta'; -import { DEFAULT_TIMEOUT } from './constants'; - -jest.mock('./catch_retryable_es_client_errors'); - -describe('updateTargetMappingsMeta', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - // Create a mock client that rejects all methods with a 503 status code - // response. - const retryableError = new EsErrors.ResponseError( - elasticsearchClientMock.createApiResponse({ - statusCode: 503, - body: { error: { type: 'es_type', reason: 'es_reason' } }, - }) - ); - const client = elasticsearchClientMock.createInternalClient( - elasticsearchClientMock.createErrorTransportRequestPromise(retryableError) - ); - - it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = updateTargetMappingsMeta({ - client, - index: 'new_index', - meta: {}, - }); - try { - await task(); - } catch (e) { - /** ignore */ - } - - expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); - }); - - it('updates the _meta information of the desired index', async () => { - const task = updateTargetMappingsMeta({ - client, - index: 'new_index', - meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', - 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', - }, - }, - }); - try { - await task(); - } catch (e) { - /** ignore */ - } - - expect(client.indices.putMapping).toHaveBeenCalledTimes(1); - expect(client.indices.putMapping).toHaveBeenCalledWith({ - index: 'new_index', - timeout: DEFAULT_TIMEOUT, - _meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', - 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', - }, - }, - }); - }); -}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts deleted file mode 100644 index 05f954b38fe71..0000000000000 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 Either from 'fp-ts/lib/Either'; -import * as TaskEither from 'fp-ts/lib/TaskEither'; - -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { IndexMappingMeta } from '@kbn/core-saved-objects-base-server-internal'; - -import { - catchRetryableEsClientErrors, - RetryableEsClientError, -} from './catch_retryable_es_client_errors'; -import { DEFAULT_TIMEOUT } from './constants'; - -/** @internal */ -export interface UpdateTargetMappingsMetaParams { - client: ElasticsearchClient; - index: string; - meta?: IndexMappingMeta; -} -/** - * Updates an index's mappings _meta information - */ -export const updateTargetMappingsMeta = - ({ - client, - index, - meta, - }: UpdateTargetMappingsMetaParams): TaskEither.TaskEither< - RetryableEsClientError, - 'update_mappings_meta_succeeded' - > => - () => { - return client.indices - .putMapping({ - index, - timeout: DEFAULT_TIMEOUT, - _meta: meta || {}, - }) - .then(() => { - // Ignore `acknowledged: false`. When the coordinating node accepts - // the new cluster state update but not all nodes have applied the - // update within the timeout `acknowledged` will be false. However, - // retrying this update will always immediately result in `acknowledged: - // true` even if there are still nodes which are falling behind with - // cluster state updates. - return Either.right('update_mappings_meta_succeeded' as const); - }) - .catch(catchRetryableEsClientErrors); - }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 19ef5d66c0eb5..15691632a4399 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -107,12 +107,12 @@ export function addExcludedTypesToBoolQuery( /** * Add the given clauses to the 'must' of the given query + * @param filterClauses the clauses to be added to a 'must' * @param boolQuery the bool query to be enriched - * @param mustClauses the clauses to be added to a 'must' * @returns a new query container with the enriched query */ export function addMustClausesToBoolQuery( - mustClauses: QueryDslQueryContainer[], + filterClauses: QueryDslQueryContainer[], boolQuery?: QueryDslBoolQuery ): QueryDslQueryContainer { let must: QueryDslQueryContainer[] = []; @@ -121,7 +121,7 @@ export function addMustClausesToBoolQuery( must = must.concat(boolQuery.must); } - must.push(...mustClauses); + must.push(...filterClauses); return { bool: { @@ -133,8 +133,8 @@ export function addMustClausesToBoolQuery( /** * Add the given clauses to the 'must_not' of the given query - * @param boolQuery the bool query to be enriched * @param filterClauses the clauses to be added to a 'must_not' + * @param boolQuery the bool query to be enriched * @returns a new query container with the enriched query */ export function addMustNotClausesToBoolQuery( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index c07538d1c1184..4eccf11e6a65b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -13,6 +13,7 @@ import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal' import type { BaseState, CalculateExcludeFiltersState, + UpdateSourceMappingsState, CheckTargetMappingsState, CheckUnknownDocumentsState, CheckVersionIndexReadyActions, @@ -1298,13 +1299,12 @@ describe('migrations v2 model', () => { sourceIndexMappings: actualMappings, }; - test('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS', () => { + test('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS', () => { const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); const newState = model(changedMappingsState, res); - expect(newState.controlState).toEqual('CHECK_UNKNOWN_DOCUMENTS'); expect(newState).toMatchObject({ - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'UPDATE_SOURCE_MAPPINGS', sourceIndex: Option.some('.kibana_7.11.0_001'), sourceIndexMappings: actualMappings, }); @@ -1330,6 +1330,49 @@ describe('migrations v2 model', () => { }); }); + describe('UPDATE_SOURCE_MAPPINGS', () => { + const checkCompatibleMappingsState: UpdateSourceMappingsState = { + ...baseState, + controlState: 'UPDATE_SOURCE_MAPPINGS', + sourceIndex: Option.some('.kibana_7.11.0_001') as Option.Some, + sourceIndexMappings: baseState.targetIndexMappings, + aliases: { + '.kibana': '.kibana_7.11.0_001', + '.kibana_7.11.0': '.kibana_7.11.0_001', + }, + }; + + describe('if action succeeds', () => { + test('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED', () => { + const res: ResponseType<'UPDATE_SOURCE_MAPPINGS'> = Either.right( + 'update_mappings_succeeded' as const + ); + const newState = model(checkCompatibleMappingsState, res); + + expect(newState).toMatchObject({ + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + targetIndex: '.kibana_7.11.0_001', + versionIndexReadyActions: Option.none, + }); + }); + }); + + describe('if action fails', () => { + test('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS', () => { + const res: ResponseType<'UPDATE_SOURCE_MAPPINGS'> = Either.left({ + type: 'incompatible_mapping_exception', + }); + const newState = model(checkCompatibleMappingsState, res); + + expect(newState).toMatchObject({ + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + sourceIndex: Option.some('.kibana_7.11.0_001'), + sourceIndexMappings: baseState.targetIndexMappings, + }); + }); + }); + }); + describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { const cleanupUnknownAndExcluded: CleanupUnknownAndExcluded = { ...baseState, @@ -2693,7 +2736,7 @@ describe('migrations v2 model', () => { test('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS if the mapping _meta information is successfully updated', () => { const res: ResponseType<'UPDATE_TARGET_MAPPINGS_META'> = Either.right( - 'update_mappings_meta_succeeded' + 'update_mappings_succeeded' ); const newState = model(updateTargetMappingsMetaState, res) as CheckVersionIndexReadyActions; expect(newState.controlState).toBe('CHECK_VERSION_INDEX_READY_ACTIONS'); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 4c2a9147eb125..f1cb94d276b35 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -424,7 +424,6 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'WAIT_FOR_YELLOW_SOURCE') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - // check the existing mappings to see if we can avoid reindexing if ( // source exists Boolean(stateP.sourceIndexMappings._meta?.migrationMappingPropertyHashes) && @@ -434,9 +433,9 @@ export const model = (currentState: State, resW: ResponseType): stateP.sourceIndexMappings, /* expected */ stateP.targetIndexMappings - ) && - Math.random() < 10 + ) ) { + // the existing mappings match, we can avoid reindexing return { ...stateP, controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', @@ -446,7 +445,7 @@ export const model = (currentState: State, resW: ResponseType): } else { return { ...stateP, - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'UPDATE_SOURCE_MAPPINGS', }; } } else if (Either.isLeft(res)) { @@ -465,6 +464,28 @@ export const model = (currentState: State, resW: ResponseType): } else { return throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'UPDATE_SOURCE_MAPPINGS') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + targetIndex: stateP.sourceIndex.value!, // We preserve the same index, source == target (E.g: ".xx8.7.0_001") + versionIndexReadyActions: Option.none, + }; + } else if (Either.isLeft(res)) { + const left = res.left; + if (isTypeof(left, 'incompatible_mapping_exception')) { + return { + ...stateP, + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + }; + } else { + return throwBadResponse(stateP, left as never); + } + } else { + return throwBadResponse(stateP, res); + } } else if (stateP.controlState === 'CLEANUP_UNKNOWN_AND_EXCLUDED') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 605dd149855e7..8cebce9995900 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -7,44 +7,46 @@ */ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { omit } from 'lodash'; import type { AllActionStates, - ReindexSourceToTempOpenPit, - ReindexSourceToTempRead, - ReindexSourceToTempClosePit, - ReindexSourceToTempTransform, - MarkVersionIndexReady, + CalculateExcludeFiltersState, + UpdateSourceMappingsState, + CheckTargetMappingsState, + CheckUnknownDocumentsState, + CleanupUnknownAndExcluded, + CleanupUnknownAndExcludedWaitForTaskState, + CloneTempToSource, + CreateNewTargetState, + CreateReindexTempState, InitState, LegacyCreateReindexTargetState, LegacyDeleteState, LegacyReindexState, LegacyReindexWaitForTaskState, LegacySetWriteBlockState, + MarkVersionIndexReady, + MarkVersionIndexReadyConflict, + OutdatedDocumentsRefresh, + OutdatedDocumentsSearchClosePit, + OutdatedDocumentsSearchOpenPit, + OutdatedDocumentsSearchRead, OutdatedDocumentsTransform, + PrepareCompatibleMigration, + RefreshTarget, + ReindexSourceToTempClosePit, + ReindexSourceToTempIndexBulk, + ReindexSourceToTempOpenPit, + ReindexSourceToTempRead, + ReindexSourceToTempTransform, SetSourceWriteBlockState, + SetTempWriteBlock, State, + TransformedDocumentsBulkIndex, UpdateTargetMappingsState, UpdateTargetMappingsWaitForTaskState, - CreateReindexTempState, - MarkVersionIndexReadyConflict, - CreateNewTargetState, - CloneTempToSource, - SetTempWriteBlock, - WaitForYellowSourceState, - TransformedDocumentsBulkIndex, - ReindexSourceToTempIndexBulk, - OutdatedDocumentsSearchOpenPit, - OutdatedDocumentsSearchRead, - OutdatedDocumentsSearchClosePit, - RefreshTarget, - OutdatedDocumentsRefresh, - CheckUnknownDocumentsState, - CalculateExcludeFiltersState, WaitForMigrationCompletionState, - CheckTargetMappingsState, - PrepareCompatibleMigration, - CleanupUnknownAndExcluded, - CleanupUnknownAndExcludedWaitForTaskState, + WaitForYellowSourceState, } from './state'; import type { TransformRawDocs } from './types'; import * as Actions from './actions'; @@ -70,6 +72,12 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => Actions.waitForIndexStatus({ client, index: state.sourceIndex.value, status: 'yellow' }), + UPDATE_SOURCE_MAPPINGS: (state: UpdateSourceMappingsState) => + Actions.updateMappings({ + client, + index: state.sourceIndex.value, // attempt to update source mappings in-place + mappings: omit(state.targetIndexMappings, ['_meta']), // ._meta property will be updated on a later step + }), CLEANUP_UNKNOWN_AND_EXCLUDED: (state: CleanupUnknownAndExcluded) => Actions.cleanupUnknownAndExcluded({ client, @@ -163,7 +171,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.updateAndPickupMappings({ client, index: state.targetIndex, - mappings: state.targetIndexMappings, + mappings: omit(state.targetIndexMappings, ['_meta']), // ._meta property will be updated on a later step }), UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK: (state: UpdateTargetMappingsWaitForTaskState) => Actions.waitForPickupUpdatedMappingsTask({ @@ -172,10 +180,10 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra timeout: '60s', }), UPDATE_TARGET_MAPPINGS_META: (state: UpdateTargetMappingsState) => - Actions.updateTargetMappingsMeta({ + Actions.updateMappings({ client, index: state.targetIndex, - meta: state.targetIndexMappings._meta, + mappings: state.targetIndexMappings, }), CHECK_VERSION_INDEX_READY_ACTIONS: () => Actions.noop, OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT: (state: OutdatedDocumentsSearchOpenPit) => diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index a091a2972343f..4ac550c89ff58 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -243,6 +243,13 @@ export interface WaitForYellowSourceState extends BaseWithSource { readonly aliases: Record; } +export interface UpdateSourceMappingsState extends BaseState { + readonly controlState: 'UPDATE_SOURCE_MAPPINGS'; + readonly sourceIndex: Option.Some; + readonly sourceIndexMappings: IndexMapping; + readonly aliases: Record; +} + export interface CheckUnknownDocumentsState extends BaseWithSource { /** Check if any unknown document is present in the source index */ readonly controlState: 'CHECK_UNKNOWN_DOCUMENTS'; @@ -493,6 +500,7 @@ export type State = Readonly< | WaitForMigrationCompletionState | DoneState | WaitForYellowSourceState + | UpdateSourceMappingsState | CheckUnknownDocumentsState | SetSourceWriteBlockState | CalculateExcludeFiltersState diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts index de529ccb07d9d..b63c75e86a562 100644 --- a/packages/core/versioning/core-version-http-server/src/example.ts +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -22,7 +22,7 @@ const versionedRouter = vtk.createVersionedRouter({ router }); const versionedRoute = versionedRouter .post({ path: '/api/my-app/foo/{id?}', - options: { timeout: { payload: 60000 } }, + options: { timeout: { payload: 60000 }, access: 'public' }, }) .addVersion( { diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 719e0075c0070..7d8dd7765e476 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -13,6 +13,7 @@ import type { RequestHandler, RouteValidatorFullConfig, RequestHandlerContextBase, + RouteConfigOptions, } from '@kbn/core-http-server'; type RqCtx = RequestHandlerContextBase; @@ -45,7 +46,7 @@ export interface CreateVersionedRouterArgs { * const versionedRoute = versionedRouter * .post({ * path: '/api/my-app/foo/{id?}', - * options: { timeout: { payload: 60000 } }, + * options: { timeout: { payload: 60000 }, access: 'public' }, * }) * .addVersion( * { @@ -99,14 +100,28 @@ export interface VersionHTTPToolkit { ): VersionedRouter; } +/** + * Converts an input property from optional to required. Needed for making RouteConfigOptions['access'] required. + */ +type WithRequiredProperty = Type & { + [Property in Key]-?: Type[Property]; +}; + +/** + * Versioned route access flag, required + * - '/api/foo' is 'public' + * - '/internal/my-foo' is 'internal' + * Required + */ +type VersionedRouteConfigOptions = WithRequiredProperty, 'access'>; /** * Configuration for a versioned route * @experimental */ export type VersionedRouteConfig = Omit< RouteConfig, - 'validate' ->; + 'validate' | 'options' +> & { options: VersionedRouteConfigOptions }; /** * Create an {@link VersionedRoute | versioned route}. diff --git a/packages/kbn-apm-synthtrace-client/index.ts b/packages/kbn-apm-synthtrace-client/index.ts index 82f8efe28b40a..1868cb188582e 100644 --- a/packages/kbn-apm-synthtrace-client/index.ts +++ b/packages/kbn-apm-synthtrace-client/index.ts @@ -20,6 +20,7 @@ export type { } from './src/lib/apm/mobile_device'; export { httpExitSpan } from './src/lib/apm/span'; export { DistributedTrace } from './src/lib/dsl/distributed_trace_client'; +export { serviceMap } from './src/lib/dsl/service_map'; export type { Fields } from './src/lib/entity'; export type { Serializable } from './src/lib/serializable'; export { timerange } from './src/lib/timerange'; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts new file mode 100644 index 0000000000000..90078f584c172 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts @@ -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 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 { pick } from 'lodash'; +import { ApmFields } from '../apm/apm_fields'; +import { BaseSpan } from '../apm/base_span'; +import { serviceMap, ServiceMapOpts } from './service_map'; + +describe('serviceMap', () => { + const TIMESTAMP = 1677693600000; + + describe('Basic definition', () => { + const BASIC_SERVICE_MAP_OPTS: ServiceMapOpts = { + services: [ + 'frontend-rum', + 'frontend-node', + 'advertService', + 'checkoutService', + 'cartService', + 'paymentService', + 'productCatalogService', + ], + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [rum, node, adv, 'elasticsearch'], + [rum, node, cart, 'redis'], + [rum, node, chk, pay], + [chk, cart, 'redis'], + [rum, node, prod, 'elasticsearch'], + [chk, prod], + ]; + }, + }; + + it('should create an accurate set of trace paths', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + expect(transactions.map(getTracePathLabel)).toMatchInlineSnapshot(` + Array [ + "frontend-rum → frontend-node → advertService → elasticsearch", + "frontend-rum → frontend-node → cartService → redis", + "frontend-rum → frontend-node → checkoutService → paymentService", + "checkoutService → cartService → redis", + "frontend-rum → frontend-node → productCatalogService → elasticsearch", + "checkoutService → productCatalogService", + ] + `); + }); + + it('should use a default agent name if not defined', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.flatMap(getTraceDocsSubset); + for (const doc of traceDocs) { + expect(doc).toHaveProperty(['agent.name'], 'nodejs'); + } + }); + + it('should use a default transaction/span names if not defined', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.map(getTraceDocsSubset); + for (let i = 0; i < traceDocs.length; i++) { + for (const doc of traceDocs[i]) { + const serviceName = doc['service.name']; + if (doc['processor.event'] === 'transaction') { + expect(doc).toHaveProperty(['transaction.name'], `GET /api/${serviceName}/${i}`); + } + if (doc['processor.event'] === 'span') { + if (doc['span.type'] === 'db') { + switch (doc['span.subtype']) { + case 'elasticsearch': + expect(doc).toHaveProperty(['span.name'], `GET ad-*/_search`); + break; + case 'redis': + expect(doc).toHaveProperty(['span.name'], `INCR item:i012345:count`); + break; + case 'sqlite': + expect(doc).toHaveProperty(['span.name'], `SELECT * FROM items`); + break; + } + } else { + expect(doc).toHaveProperty(['span.name'], `GET /api/${serviceName}/${i}`); + } + } + } + } + }); + + it('should create one parent transaction per trace', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traces = transactions.map(getTraceDocsSubset); + for (const traceDocs of traces) { + const [transaction, ...spans] = traceDocs; + expect(transaction).toHaveProperty(['processor.event'], 'transaction'); + expect( + spans.every(({ 'processor.event': processorEvent }) => processorEvent === 'span') + ).toBe(true); + } + }); + }); + describe('Detailed definition', () => { + const DETAILED_SERVICE_MAP_OPTS: ServiceMapOpts = { + services: [ + { 'frontend-rum': 'rum-js' }, + { 'frontend-node': 'nodejs' }, + { advertService: 'java' }, + { checkoutService: 'go' }, + { cartService: 'dotnet' }, + { paymentService: 'nodejs' }, + { productCatalogService: 'go' }, + ], + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [ + [rum, 'fetchAd'], + [node, 'GET /nodejs/adTag'], + [adv, 'APIRestController#getAd'], + ['elasticsearch', 'GET ad-*/_search'], + ], + [ + [rum, 'AddToCart'], + [node, 'POST /nodejs/addToCart'], + [cart, 'POST /dotnet/reserveProduct'], + ['redis', 'DECR inventory:i012345:stock'], + ], + { + path: [ + [rum, 'Checkout'], + [node, 'POST /nodejs/placeOrder'], + [chk, 'POST /go/placeOrder'], + [pay, 'POST /nodejs/processPayment'], + ], + transaction: (t) => t.defaults({ 'labels.name': 'transaction hook test' }), + }, + [ + [chk, 'POST /go/clearCart'], + [cart, 'PUT /dotnet/cart/c12345/reset'], + ['redis', 'INCR inventory:i012345:stock'], + ], + [ + [rum, 'ProductDashboard'], + [node, 'GET /nodejs/products'], + [prod, 'GET /go/product-catalog'], + ['elasticsearch', 'GET product-*/_search'], + ], + [ + [chk, 'PUT /go/update-inventory'], + [prod, 'PUT /go/product/i012345'], + ], + [pay], + ]; + }, + }; + + const SERVICE_AGENT_MAP: Record = { + 'frontend-rum': 'rum-js', + 'frontend-node': 'nodejs', + advertService: 'java', + checkoutService: 'go', + cartService: 'dotnet', + paymentService: 'nodejs', + productCatalogService: 'go', + }; + + it('should use the defined agent name for a given service', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.flatMap(getTraceDocsSubset); + for (const doc of traceDocs) { + if (!(doc['service.name']! in SERVICE_AGENT_MAP)) { + throw new Error(`Unexpected service name '${doc['service.name']}' found`); + } + + expect(doc).toHaveProperty(['agent.name'], SERVICE_AGENT_MAP[doc['service.name']!]); + } + }); + + it('should use the defined transaction/span names for each trace document', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.map((transaction) => { + return getTraceDocsSubset(transaction).map( + ({ 'span.name': spanName, 'transaction.name': transactionName }) => + transactionName || spanName + ); + }); + expect(traceDocs).toMatchInlineSnapshot(` + Array [ + Array [ + "fetchAd", + "fetchAd", + "GET /nodejs/adTag", + "APIRestController#getAd", + "GET ad-*/_search", + ], + Array [ + "AddToCart", + "AddToCart", + "POST /nodejs/addToCart", + "POST /dotnet/reserveProduct", + "DECR inventory:i012345:stock", + ], + Array [ + "Checkout", + "Checkout", + "POST /nodejs/placeOrder", + "POST /go/placeOrder", + "POST /nodejs/processPayment", + ], + Array [ + "POST /go/clearCart", + "POST /go/clearCart", + "PUT /dotnet/cart/c12345/reset", + "INCR inventory:i012345:stock", + ], + Array [ + "ProductDashboard", + "ProductDashboard", + "GET /nodejs/products", + "GET /go/product-catalog", + "GET product-*/_search", + ], + Array [ + "PUT /go/update-inventory", + "PUT /go/update-inventory", + "PUT /go/product/i012345", + ], + Array [ + "GET /api/paymentService/6", + "GET /api/paymentService/6", + ], + ] + `); + }); + + it('should apply the transaction hook function if defined', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + expect(transactions[2].fields['labels.name']).toBe('transaction hook test'); + }); + }); +}); + +function getTraceDocsSubset(transaction: BaseSpan): ApmFields[] { + const subsetFields = pick(transaction.fields, [ + 'processor.event', + 'service.name', + 'agent.name', + 'transaction.name', + 'span.name', + 'span.type', + 'span.subtype', + 'span.destination.service.resource', + ]); + + const children = transaction.getChildren(); + if (children) { + const childFields = children.flatMap((child) => getTraceDocsSubset(child)); + return [subsetFields, ...childFields]; + } + return [subsetFields]; +} + +function getTracePathLabel(transaction: BaseSpan) { + const traceDocs = getTraceDocsSubset(transaction); + const traceSpans = traceDocs.filter((doc) => doc['processor.event'] === 'span'); + const spanLabels = traceSpans.map((span) => + span['span.type'] === 'db' ? span['span.subtype'] : span['service.name'] + ); + return spanLabels.join(' → '); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts new file mode 100644 index 0000000000000..4c91352b11d02 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts @@ -0,0 +1,156 @@ +/* + * 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 { AgentName } from '../../types/agent_names'; +import { apm } from '../apm'; +import { Instance } from '../apm/instance'; +import { elasticsearchSpan, redisSpan, sqliteSpan, Span } from '../apm/span'; +import { Transaction } from '../apm/transaction'; + +const ENVIRONMENT = 'Synthtrace: service_map'; + +function service(serviceName: string, agentName: AgentName, environment?: string) { + return apm + .service({ name: serviceName, environment: environment || ENVIRONMENT, agentName }) + .instance(serviceName); +} + +type DbSpan = 'elasticsearch' | 'redis' | 'sqlite'; +type ServiceMapNode = Instance | DbSpan; +type TransactionName = string; +type TraceItem = ServiceMapNode | [ServiceMapNode, TransactionName]; +type TracePath = TraceItem[]; + +function getTraceItem(traceItem: TraceItem) { + if (Array.isArray(traceItem)) { + const transactionName = traceItem[1]; + if (typeof traceItem[0] === 'string') { + const dbSpan = traceItem[0]; + return { dbSpan, transactionName, serviceInstance: undefined }; + } else { + const serviceInstance = traceItem[0]; + return { dbSpan: undefined, transactionName, serviceInstance }; + } + } else if (typeof traceItem === 'string') { + const dbSpan = traceItem; + return { dbSpan, transactionName: undefined, serviceInstance: undefined }; + } else { + const serviceInstance = traceItem; + return { dbSpan: undefined, transactionName: undefined, serviceInstance }; + } +} + +function getTransactionName( + transactionName: string | undefined, + serviceInstance: Instance, + index: number +) { + return transactionName || `GET /api/${serviceInstance.fields['service.name']}/${index}`; +} + +function getChildren( + childTraceItems: TracePath, + parentServiceInstance: Instance, + timestamp: number, + index: number +): Span[] { + if (childTraceItems.length === 0) { + return []; + } + const [first, ...rest] = childTraceItems; + const { dbSpan, serviceInstance, transactionName } = getTraceItem(first); + if (dbSpan) { + switch (dbSpan) { + case 'elasticsearch': + return [ + parentServiceInstance + .span(elasticsearchSpan(transactionName || 'GET ad-*/_search')) + .timestamp(timestamp) + .duration(1000), + ]; + case 'redis': + return [ + parentServiceInstance + .span(redisSpan(transactionName || 'INCR item:i012345:count')) + .timestamp(timestamp) + .duration(1000), + ]; + case 'sqlite': + return [ + parentServiceInstance + .span(sqliteSpan(transactionName || 'SELECT * FROM items')) + .timestamp(timestamp) + .duration(1000), + ]; + } + } + const childSpan = serviceInstance + .span({ + spanName: getTransactionName(transactionName, serviceInstance, index), + spanType: 'app', + }) + .timestamp(timestamp) + .duration(1000) + .children(...getChildren(rest, serviceInstance, timestamp, index)); + if (rest[0]) { + const next = getTraceItem(rest[0]); + if (next.serviceInstance) { + return [childSpan.destination(next.serviceInstance.fields['service.name']!)]; + } + } + return [childSpan]; +} + +interface TracePathOpts { + path: TracePath; + transaction?: (transaction: Transaction) => Transaction; +} +type PathDef = TracePath | TracePathOpts; +export interface ServiceMapOpts { + services: Array; + definePaths: (services: Instance[]) => PathDef[]; + environment?: string; +} + +export function serviceMap(options: ServiceMapOpts) { + const serviceInstances = options.services.map((s) => { + if (typeof s === 'string') { + return service(s, 'nodejs', options.environment); + } + return service(Object.keys(s)[0], Object.values(s)[0], options.environment); + }); + return (timestamp: number) => { + const tracePaths = options.definePaths(serviceInstances); + return tracePaths.map((traceDef, index) => { + const tracePath = 'path' in traceDef ? traceDef.path : traceDef; + const [first] = tracePath; + + const firstTraceItem = getTraceItem(first); + if (firstTraceItem.serviceInstance === undefined) { + throw new Error('First trace item must be a service instance'); + } + const transactionName = getTransactionName( + firstTraceItem.transactionName, + firstTraceItem.serviceInstance, + index + ); + + const transaction = firstTraceItem.serviceInstance + .transaction({ transactionName, transactionType: 'request' }) + .timestamp(timestamp) + .duration(1000) + .children(...getChildren(tracePath, firstTraceItem.serviceInstance, timestamp, index)); + + if ('transaction' in traceDef && traceDef.transaction) { + return traceDef.transaction(transaction); + } + + return transaction; + }); + }; +} diff --git a/packages/kbn-apm-synthtrace-client/src/types/agent_names.ts b/packages/kbn-apm-synthtrace-client/src/types/agent_names.ts new file mode 100644 index 0000000000000..d9e3a371e0e87 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/types/agent_names.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. + */ + +type ElasticAgentName = + | 'go' + | 'java' + | 'js-base' + | 'iOS/swift' + | 'rum-js' + | 'nodejs' + | 'python' + | 'dotnet' + | 'ruby' + | 'php' + | 'android/java'; + +type OpenTelemetryAgentName = + | 'otlp' + | 'opentelemetry/cpp' + | 'opentelemetry/dotnet' + | 'opentelemetry/erlang' + | 'opentelemetry/go' + | 'opentelemetry/java' + | 'opentelemetry/nodejs' + | 'opentelemetry/php' + | 'opentelemetry/python' + | 'opentelemetry/ruby' + | 'opentelemetry/swift' + | 'opentelemetry/webjs'; + +// Unable to reference AgentName from '@kbn/apm-plugin/typings/es_schemas/ui/fields/agent' due to circular reference +export type AgentName = ElasticAgentName | OpenTelemetryAgentName; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts b/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts index 41e499320fd50..f39d4bdd1e221 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts @@ -6,116 +6,71 @@ * Side Public License, v 1. */ -import { apm, ApmFields, Instance } from '@kbn/apm-synthtrace-client'; -import { Transaction } from '@kbn/apm-synthtrace-client/src/lib/apm/transaction'; -import { AgentName } from '@kbn/apm-plugin/typings/es_schemas/ui/fields/agent'; +import { ApmFields, serviceMap } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; import { RunOptions } from '../cli/utils/parse_run_cli_flags'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; -const ENVIRONMENT = getSynthtraceEnvironment(__filename); - -function generateTrace( - timestamp: number, - transactionName: string, - order: Instance[], - db?: 'elasticsearch' | 'redis' -) { - return order - .concat() - .reverse() - .reduce((prev, instance, index) => { - const invertedIndex = order.length - index - 1; - - const duration = 50; - const time = timestamp + invertedIndex * 10; - - const transaction: Transaction = instance - .transaction({ transactionName }) - .timestamp(time) - .duration(duration); - - if (prev) { - const next = order[invertedIndex + 1].fields['service.name']!; - transaction.children( - instance - .span({ spanName: `GET ${next}/api`, spanType: 'external', spanSubtype: 'http' }) - .destination(next) - .duration(duration) - .timestamp(time + 1) - .children(prev) - ); - } else if (db) { - transaction.children( - instance - .span({ spanName: db, spanType: 'db', spanSubtype: db }) - .destination(db) - .duration(duration) - .timestamp(time + 1) - ); - } - - return transaction; - }, undefined)!; -} - -function service(serviceName: string, agentName: AgentName) { - return apm - .service({ name: serviceName, environment: ENVIRONMENT, agentName }) - .instance(serviceName); -} +const environment = getSynthtraceEnvironment(__filename); const scenario: Scenario = async (runOptions: RunOptions) => { return { generate: ({ range }) => { - const frontendRum = service('frontend-rum', 'rum-js'); - const frontendNode = service('frontend-node', 'nodejs'); - const advertService = service('advertService', 'java'); - const checkoutService = service('checkoutService', 'go'); - const cartService = service('cartService', 'dotnet'); - const paymentService = service('paymentService', 'nodejs'); - const productCatalogService = service('productCatalogService', 'go'); return range .interval('1s') .rate(3) - .generator((timestamp) => { - return [ - generateTrace( - timestamp, - 'GET /api/adTag', - [frontendRum, frontendNode, advertService], - 'elasticsearch' - ), - generateTrace( - timestamp, - 'POST /api/addToCart', - [frontendRum, frontendNode, cartService], - 'redis' - ), - generateTrace(timestamp, 'POST /api/checkout', [ - frontendRum, - frontendNode, - checkoutService, - paymentService, - ]), - generateTrace( - timestamp, - 'DELETE /api/clearCart', - [checkoutService, cartService], - 'redis' - ), - generateTrace( - timestamp, - 'GET /api/products', - [frontendRum, frontendNode, productCatalogService], - 'elasticsearch' - ), - generateTrace(timestamp, 'PUT /api/updateInventory', [ - checkoutService, - productCatalogService, - ]), - ]; - }); + .generator( + serviceMap({ + services: [ + { 'frontend-rum': 'rum-js' }, + { 'frontend-node': 'nodejs' }, + { advertService: 'java' }, + { checkoutService: 'go' }, + { cartService: 'dotnet' }, + { paymentService: 'nodejs' }, + { productCatalogService: 'go' }, + ], + environment, + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [ + [rum, 'fetchAd'], + [node, 'GET /nodejs/adTag'], + [adv, 'APIRestController#getAd'], + ['elasticsearch', 'GET ad-*/_search'], + ], + [ + [rum, 'AddToCart'], + [node, 'POST /nodejs/addToCart'], + [cart, 'POST /dotnet/reserveProduct'], + ['redis', 'DECR inventory:i012345:stock'], + ], + [ + [rum, 'Checkout'], + [node, 'POST /nodejs/placeOrder'], + [chk, 'POST /go/placeOrder'], + [pay, 'POST /nodejs/processPayment'], + ], + [ + [chk, 'POST /go/clearCart'], + [cart, 'PUT /dotnet/cart/c12345/reset'], + ['redis', 'INCR inventory:i012345:stock'], + ], + [ + [rum, 'ProductDashboard'], + [node, 'GET /nodejs/products'], + [prod, 'GET /go/product-catalog'], + ['elasticsearch', 'GET product-*/_search'], + ], + [ + [chk, 'PUT /go/update-inventory'], + [prod, 'PUT /go/product/i012345'], + ], + [pay], + ]; + }, + }) + ); }, }; }; diff --git a/packages/kbn-apm-synthtrace/tsconfig.json b/packages/kbn-apm-synthtrace/tsconfig.json index 3db0ec03f6f4d..22ff0442879ab 100644 --- a/packages/kbn-apm-synthtrace/tsconfig.json +++ b/packages/kbn-apm-synthtrace/tsconfig.json @@ -8,7 +8,6 @@ "kbn_references": [ "@kbn/datemath", "@kbn/apm-synthtrace-client", - "@kbn/apm-plugin" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 83b9882118b1c..986ec051637e4 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -91,7 +91,7 @@ export function getWebpackConfig( // already bundled with all its necessary dependencies noParse: [ /[\/\\]node_modules[\/\\]lodash[\/\\]index\.js$/, - /[\/\\]node_modules[\/\\]vega[\/\\]build[\/\\]vega\.js$/, + /[\/\\]node_modules[\/\\]vega[\/\\]build-es5[\/\\]vega\.js$/, ], rules: [ diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap index 3311b524657af..e23da950c3486 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap @@ -185,7 +185,7 @@ Object { - Manage rules + Link rules @@ -565,7 +565,7 @@ Object { - Manage rules + Link rules @@ -848,7 +848,7 @@ Object { - Manage rules + Link rules @@ -1074,7 +1074,7 @@ Object { - Manage rules + Link rules @@ -1329,7 +1329,7 @@ Object { - Manage rules + Link rules @@ -1528,7 +1528,7 @@ Object { - Manage rules + Link rules diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap index ca6a0b1d4018c..16bda1172b17c 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap @@ -242,7 +242,7 @@ Object { - Manage rules + Link rules @@ -309,7 +309,7 @@ Object { - Manage rules + Link rules @@ -433,7 +433,7 @@ Object { - Manage rules + Link rules @@ -580,7 +580,7 @@ Object { - Manage rules + Link rules @@ -1022,7 +1022,7 @@ Object { - Manage rules + Link rules @@ -1117,7 +1117,7 @@ Object { - Manage rules + Link rules diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx index 14aa823046ff4..9062162b30a47 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx @@ -81,7 +81,7 @@ const MenuItemsComponent: FC = ({ if (typeof onManageRules === 'function') onManageRules(); }} > - {i18n.EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON} + {i18n.EXCEPTION_LIST_HEADER_LINK_RULES_BUTTON} )} diff --git a/packages/kbn-securitysolution-exception-list-components/src/translations.ts b/packages/kbn-securitysolution-exception-list-components/src/translations.ts index c80a6a07a6697..b769b9b10846a 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/translations.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/translations.ts @@ -67,10 +67,10 @@ export const EXCEPTION_LIST_HEADER_DELETE_ACTION = i18n.translate( defaultMessage: 'Delete exception list', } ); -export const EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON = i18n.translate( - 'exceptionList-components.exception_list_header_manage_rules_button', +export const EXCEPTION_LIST_HEADER_LINK_RULES_BUTTON = i18n.translate( + 'exceptionList-components.exception_list_header_link_rules_button', { - defaultMessage: 'Manage rules', + defaultMessage: 'Link rules', } ); diff --git a/packages/kbn-securitysolution-grouping/index.tsx b/packages/kbn-securitysolution-grouping/index.tsx index c9b970a1aed77..1a0907e42d654 100644 --- a/packages/kbn-securitysolution-grouping/index.tsx +++ b/packages/kbn-securitysolution-grouping/index.tsx @@ -18,7 +18,7 @@ import { } from './src'; import type { NamedAggregation, GroupingFieldTotalAggregation, GroupingAggregation } from './src'; -export const getGrouping = (props: GroupingProps): React.ReactElement => ( +export const getGrouping = (props: GroupingProps): React.ReactElement> => ( ); diff --git a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/group_stats.tsx b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/group_stats.tsx index 6de1bf6ef4678..5aadbeed897f9 100644 --- a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/group_stats.tsx +++ b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/group_stats.tsx @@ -21,21 +21,21 @@ import { statsContainerCss } from '../styles'; import { TAKE_ACTION } from '../translations'; import type { RawBucket } from '../types'; -interface GroupStatsProps { +interface GroupStatsProps { badgeMetricStats?: BadgeMetric[]; - bucket: RawBucket; + bucket: RawBucket; customMetricStats?: CustomMetric[]; onTakeActionsOpen?: () => void; takeActionItems: JSX.Element[]; } -const GroupStatsComponent = ({ +const GroupStatsComponent = ({ badgeMetricStats, bucket, customMetricStats, onTakeActionsOpen, takeActionItems, -}: GroupStatsProps) => { +}: GroupStatsProps) => { const [isPopoverOpen, setPopover] = useState(false); const onButtonClick = useCallback( diff --git a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx index 124c4ede22485..2514a0e8bd6ff 100644 --- a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx +++ b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx @@ -25,16 +25,16 @@ export interface CustomMetric { customStatRenderer: JSX.Element; } -interface GroupPanelProps { +interface GroupPanelProps { customAccordionButtonClassName?: string; customAccordionClassName?: string; extraAction?: React.ReactNode; forceState?: 'open' | 'closed'; - groupBucket: RawBucket; + groupBucket: RawBucket; groupPanelRenderer?: JSX.Element; isLoading: boolean; level?: number; - onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void; + onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void; renderChildComponent: (groupFilter: Filter[]) => React.ReactNode; selectedGroup: string; } @@ -51,7 +51,7 @@ const DefaultGroupPanelRenderer = ({ title }: { title: string }) => (

    ); -const GroupPanelComponent = ({ +const GroupPanelComponent = ({ customAccordionButtonClassName = 'groupingAccordionForm__button', customAccordionClassName = 'groupingAccordionForm', extraAction, @@ -63,7 +63,7 @@ const GroupPanelComponent = ({ onToggleGroup, renderChildComponent, selectedGroup, -}: GroupPanelProps) => { +}: GroupPanelProps) => { const groupFieldValue = useMemo(() => firstNonNullValue(groupBucket.key), [groupBucket.key]); const groupFilters = useMemo( diff --git a/packages/kbn-securitysolution-grouping/src/components/grouping.tsx b/packages/kbn-securitysolution-grouping/src/components/grouping.tsx index 8d85df8710f95..e22414c14ca5f 100644 --- a/packages/kbn-securitysolution-grouping/src/components/grouping.tsx +++ b/packages/kbn-securitysolution-grouping/src/components/grouping.tsx @@ -25,11 +25,11 @@ import { groupingContainerCss, groupsUnitCountCss } from './styles'; import { GROUPS_UNIT } from './translations'; import type { GroupingAggregation, GroupingFieldTotalAggregation, RawBucket } from './types'; -export interface GroupingProps { - badgeMetricStats?: (fieldBucket: RawBucket) => BadgeMetric[]; - customMetricStats?: (fieldBucket: RawBucket) => CustomMetric[]; - data?: GroupingAggregation & GroupingFieldTotalAggregation; - groupPanelRenderer?: (fieldBucket: RawBucket) => JSX.Element | undefined; +export interface GroupingProps { + badgeMetricStats?: (fieldBucket: RawBucket) => BadgeMetric[]; + customMetricStats?: (fieldBucket: RawBucket) => CustomMetric[]; + data?: GroupingAggregation & GroupingFieldTotalAggregation; + groupPanelRenderer?: (fieldBucket: RawBucket) => JSX.Element | undefined; groupsSelector?: JSX.Element; inspectButton?: JSX.Element; isLoading: boolean; @@ -46,7 +46,7 @@ export interface GroupingProps { unit?: (n: number) => string; } -const GroupingComponent = ({ +const GroupingComponent = ({ badgeMetricStats, customMetricStats, data, @@ -59,9 +59,9 @@ const GroupingComponent = ({ selectedGroup, takeActionItems, unit = defaultUnit, -}: GroupingProps) => { +}: GroupingProps) => { const [trigger, setTrigger] = useState< - Record + Record }> >({}); const groupsNumber = data?.groupsNumber?.value ?? 0; @@ -196,4 +196,4 @@ const GroupingComponent = ({ ); }; -export const Grouping = React.memo(GroupingComponent); +export const Grouping = React.memo(GroupingComponent) as typeof GroupingComponent; diff --git a/packages/kbn-securitysolution-grouping/src/components/types.ts b/packages/kbn-securitysolution-grouping/src/components/types.ts index 5a86bae394dd5..6885ab2f07e12 100644 --- a/packages/kbn-securitysolution-grouping/src/components/types.ts +++ b/packages/kbn-securitysolution-grouping/src/components/types.ts @@ -15,41 +15,12 @@ export interface GenericBuckets { export const NONE_GROUP_KEY = 'none'; -export type RawBucket = GenericBuckets & { - alertsCount?: { - value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed - }; - severitiesSubAggregation?: { - buckets?: GenericBuckets[]; - }; - countSeveritySubAggregation?: { - value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed - }; - usersCountAggregation?: { - value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed - }; - hostsCountAggregation?: { - value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed - }; - rulesCountAggregation?: { - value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed - }; - ruleTags?: { - doc_count_error_upper_bound?: number; - sum_other_doc_count?: number; - buckets?: GenericBuckets[]; - }; - stackByMultipleFields1?: { - buckets?: GenericBuckets[]; - doc_count_error_upper_bound?: number; - sum_other_doc_count?: number; - }; -}; +export type RawBucket = GenericBuckets & T; /** Defines the shape of the aggregation returned by Elasticsearch */ -export interface GroupingAggregation { +export interface GroupingAggregation { stackByMultipleFields0?: { - buckets?: RawBucket[]; + buckets?: Array>; }; groupsCount0?: { value?: number | null; @@ -60,11 +31,3 @@ export type GroupingFieldTotalAggregation = Record< string, { value?: number | null; buckets?: Array<{ doc_count?: number | null }> } >; - -export type FlattenedBucket = Pick< - RawBucket, - 'doc_count' | 'key' | 'key_as_string' | 'alertsCount' -> & { - stackByMultipleFields1Key?: string; - stackByMultipleFields1DocCount?: number; -}; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts index 0262e16539e9f..8c8e1d3e0db4b 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/create_endpoint_list_item_schema/index.ts @@ -22,7 +22,6 @@ import { description } from '../../common/description'; import { name } from '../../common/name'; import { meta } from '../../common/meta'; import { tags } from '../../common/tags'; -import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common'; export const createEndpointListItemSchema = t.intersection([ t.exact( @@ -40,7 +39,6 @@ export const createEndpointListItemSchema = t.intersection([ meta, // defaults to undefined if not set during decode os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode tags, // defaults to empty array if not set during decode - expire_time: expireTimeOrUndefined, // defaults to undefined if not set during decode }) ), ]); @@ -50,12 +48,11 @@ export type CreateEndpointListItemSchema = t.OutputOf>, - 'tags' | 'item_id' | 'entries' | 'comments' | 'os_types' | 'expire_time' + 'tags' | 'item_id' | 'entries' | 'comments' | 'os_types' > & { comments: CreateCommentsArray; tags: Tags; item_id: ItemId; entries: EntriesArray; os_types: OsTypeArray; - expire_time: ExpireTimeOrUndefined; }; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts index b0669b05463cf..8e5aa41e1fad2 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/update_endpoint_list_item_schema/index.ts @@ -21,7 +21,6 @@ import { Tags, tags } from '../../common/tags'; import { RequiredKeepUndefined } from '../../common/required_keep_undefined'; import { UpdateCommentsArray } from '../../common/update_comment'; import { EntriesArray } from '../../common/entries'; -import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common'; export const updateEndpointListItemSchema = t.intersection([ t.exact( @@ -41,7 +40,6 @@ export const updateEndpointListItemSchema = t.intersection([ meta, // defaults to undefined if not set during decode os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode tags, // defaults to empty array if not set during decode - expire_time: expireTimeOrUndefined, }) ), ]); @@ -51,11 +49,10 @@ export type UpdateEndpointListItemSchema = t.OutputOf>, - 'tags' | 'entries' | 'comments' | 'expire_time' + 'tags' | 'entries' | 'comments' > & { comments: UpdateCommentsArray; tags: Tags; entries: EntriesArray; os_types: OsTypeArray; - expire_time: ExpireTimeOrUndefined; }; diff --git a/packages/kbn-storybook/src/webpack.config.ts b/packages/kbn-storybook/src/webpack.config.ts index a10de36a21348..35bda9718d7cb 100644 --- a/packages/kbn-storybook/src/webpack.config.ts +++ b/packages/kbn-storybook/src/webpack.config.ts @@ -75,6 +75,10 @@ export default ({ config: storybookConfig }: { config: Configuration }) => { }, externals, module: { + // no parse rules for a few known large packages which have no require() statements + // or which have require() statements that should be ignored because the file is + // already bundled with all its necessary dependencies + noParse: [/[\/\\]node_modules[\/\\]vega[\/\\]build-es5[\/\\]vega\.js$/], rules: [ { test: /\.(html|md|txt|tmpl)$/, @@ -128,6 +132,7 @@ export default ({ config: storybookConfig }: { config: Configuration }) => { alias: { core_app_image_assets: resolve(REPO_ROOT, 'src/core/public/styles/core_app/images'), core_styles: resolve(REPO_ROOT, 'src/core/public/index.scss'), + vega: resolve(REPO_ROOT, 'node_modules/vega/build-es5/vega.js'), }, }, stats, diff --git a/src/cli_setup/utils.ts b/src/cli_setup/utils.ts index 36c08de4d422a..8f31c9eee03f7 100644 --- a/src/cli_setup/utils.ts +++ b/src/cli_setup/utils.ts @@ -49,7 +49,7 @@ export const elasticsearch = new ElasticsearchService(logger, kibanaPackageJson. logger, type, // we use an independent AgentManager for cli_setup, no need to track performance of this one - agentFactoryProvider: new AgentManager(), + agentFactoryProvider: new AgentManager(logger.get('agent-manager')), kibanaVersion: kibanaPackageJson.version, }); }, diff --git a/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip b/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip deleted file mode 100644 index f4a89fbcb2514..0000000000000 Binary files a/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip and /dev/null differ diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts index 1538e9cfe8ef4..89478ea377f6c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts @@ -24,90 +24,104 @@ async function removeLogFile() { } describe('migration from 7.13 to 7.14+ with many failed action_tasks', () => { - let esServer: TestElasticsearchUtils; - let root: Root; - let startES: () => Promise; + describe('if mappings are incompatible (reindex required)', () => { + let esServer: TestElasticsearchUtils; + let root: Root; + let startES: () => Promise; - beforeAll(async () => { - await removeLogFile(); - }); + beforeAll(async () => { + await removeLogFile(); + }); - beforeEach(() => { - ({ startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - dataArchive: Path.join(__dirname, '..', 'archives', '7.13_1.5k_failed_action_tasks.zip'), + beforeEach(() => { + ({ startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + es: { + license: 'basic', + dataArchive: Path.join( + __dirname, + '..', + 'archives', + '7.13_1.5k_failed_action_tasks.zip' + ), + }, }, - }, - })); - }); + })); + }); - afterEach(async () => { - if (root) { - await root.shutdown(); - } - if (esServer) { - await esServer.stop(); - } + afterEach(async () => { + if (root) { + await root.shutdown(); + } + if (esServer) { + await esServer.stop(); + } - await new Promise((resolve) => setTimeout(resolve, 10000)); - }); + await new Promise((resolve) => setTimeout(resolve, 10000)); + }); - const getCounts = async ( - kibanaIndexName = '.kibana', - taskManagerIndexName = '.kibana_task_manager' - ): Promise<{ tasksCount: number; actionTaskParamsCount: number }> => { - const esClient: ElasticsearchClient = esServer.es.getClient(); - - const actionTaskParamsResponse = await esClient.count({ - index: kibanaIndexName, - body: { - query: { - bool: { must: { term: { type: 'action_task_params' } } }, + const getCounts = async ( + kibanaIndexName = '.kibana', + taskManagerIndexName = '.kibana_task_manager' + ): Promise<{ tasksCount: number; actionTaskParamsCount: number }> => { + const esClient: ElasticsearchClient = esServer.es.getClient(); + + const actionTaskParamsResponse = await esClient.count({ + index: kibanaIndexName, + body: { + query: { + bool: { must: { term: { type: 'action_task_params' } } }, + }, }, - }, - }); - const tasksResponse = await esClient.count({ - index: taskManagerIndexName, - body: { - query: { - bool: { must: { term: { type: 'task' } } }, + }); + const tasksResponse = await esClient.count({ + index: taskManagerIndexName, + body: { + query: { + bool: { must: { term: { type: 'task' } } }, + }, }, - }, - }); + }); - return { - actionTaskParamsCount: actionTaskParamsResponse.count, - tasksCount: tasksResponse.count, + return { + actionTaskParamsCount: actionTaskParamsResponse.count, + tasksCount: tasksResponse.count, + }; }; - }; - - it('filters out all outdated action_task_params and action tasks', async () => { - esServer = await startES(); - - // Verify counts in current index before migration starts - expect(await getCounts()).toEqual({ - actionTaskParamsCount: 2010, - tasksCount: 2020, - }); - root = createRoot(); - await root.preboot(); - await root.setup(); - await root.start(); - - // Bulk of tasks should have been filtered out of current index - const { actionTaskParamsCount, tasksCount } = await getCounts(); - // Use toBeLessThan to avoid flakiness in the case that TM starts manipulating docs before the counts are taken - expect(actionTaskParamsCount).toBeLessThan(1000); - expect(tasksCount).toBeLessThan(1000); - - // Verify that docs were not deleted from old index - expect(await getCounts('.kibana_7.13.5_001', '.kibana_task_manager_7.13.5_001')).toEqual({ - actionTaskParamsCount: 2010, - tasksCount: 2020, + it('filters out all outdated action_task_params and action tasks', async () => { + esServer = await startES(); + + // Verify counts in current index before migration starts + expect(await getCounts()).toEqual({ + actionTaskParamsCount: 2010, + tasksCount: 2020, + }); + + root = createRoot(); + await root.preboot(); + await root.setup(); + await root.start(); + + // Bulk of tasks should have been filtered out of current index + const { actionTaskParamsCount, tasksCount } = await getCounts(); + // Use toBeLessThan to avoid flakiness in the case that TM starts manipulating docs before the counts are taken + expect(actionTaskParamsCount).toBeLessThan(1000); + expect(tasksCount).toBeLessThan(1000); + + const { + actionTaskParamsCount: oldIndexActionTaskParamsCount, + tasksCount: oldIndexTasksCount, + } = await getCounts('.kibana_7.13.5_001', '.kibana_task_manager_7.13.5_001'); + + // .kibana mappings changes are NOT compatible, we reindex and preserve old index's documents + expect(oldIndexActionTaskParamsCount).toEqual(2010); + + // ATM .kibana_task_manager mappings changes are compatible, we skip reindex and actively delete unwanted documents + // if the mappings become incompatible in the future, the we will reindex and the old index must still contain all 2020 docs + // if the mappings remain compatible, we reuse the existing index and actively delete unwanted documents from it + expect(oldIndexTasksCount === 2020 || oldIndexTasksCount < 1000).toEqual(true); }); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts index 065a4a4241d07..8a798508ce18f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts @@ -122,30 +122,30 @@ describe('migration v2', () => { // 23 saved objects + 14 corrupt (discarded) = 37 total in the old index expect((docs.hits.total as SearchTotalHits).value).toEqual(23); - expect(docs.hits.hits.map(({ _id }) => _id)).toEqual([ + expect(docs.hits.hits.map(({ _id }) => _id).sort()).toEqual([ 'config:7.13.0', 'index-pattern:logs-*', 'index-pattern:metrics-*', + 'ui-metric:console:DELETE_delete', + 'ui-metric:console:GET_get', + 'ui-metric:console:GET_search', + 'ui-metric:console:POST_delete_by_query', + 'ui-metric:console:POST_index', + 'ui-metric:console:PUT_indices.put_mapping', 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application', + 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application_unknown', + 'usage-counters:uiCounter:21052021:count:console:DELETE_delete', 'usage-counters:uiCounter:21052021:count:console:GET_cat.aliases', - 'usage-counters:uiCounter:21052021:loaded:console:opened_app', 'usage-counters:uiCounter:21052021:count:console:GET_cat.indices', + 'usage-counters:uiCounter:21052021:count:console:GET_get', + 'usage-counters:uiCounter:21052021:count:console:GET_search', + 'usage-counters:uiCounter:21052021:count:console:POST_delete_by_query', + 'usage-counters:uiCounter:21052021:count:console:POST_index', + 'usage-counters:uiCounter:21052021:count:console:PUT_indices.put_mapping', 'usage-counters:uiCounter:21052021:count:global_search_bar:search_focus', - 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application_unknown', 'usage-counters:uiCounter:21052021:count:global_search_bar:search_request', 'usage-counters:uiCounter:21052021:count:global_search_bar:shortcut_used', - 'ui-metric:console:POST_delete_by_query', - 'usage-counters:uiCounter:21052021:count:console:PUT_indices.put_mapping', - 'usage-counters:uiCounter:21052021:count:console:POST_delete_by_query', - 'usage-counters:uiCounter:21052021:count:console:GET_search', - 'ui-metric:console:PUT_indices.put_mapping', - 'ui-metric:console:GET_search', - 'usage-counters:uiCounter:21052021:count:console:DELETE_delete', - 'ui-metric:console:DELETE_delete', - 'usage-counters:uiCounter:21052021:count:console:GET_get', - 'ui-metric:console:GET_get', - 'usage-counters:uiCounter:21052021:count:console:POST_index', - 'ui-metric:console:POST_index', + 'usage-counters:uiCounter:21052021:loaded:console:opened_app', ]); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts index 19326c15e0f25..54ab116d2c596 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts @@ -8,12 +8,10 @@ import Path from 'path'; import fs from 'fs/promises'; -import JSON5 from 'json5'; import { Env } from '@kbn/config'; import { REPO_ROOT } from '@kbn/repo-info'; import { getEnvOptions } from '@kbn/config-mocks'; import { Root } from '@kbn/core-root-server-internal'; -import { LogRecord } from '@kbn/logging'; import { createRootWithCorePlugins, createTestServers, @@ -23,30 +21,14 @@ import { delay } from '../test_utils'; const logFilePath = Path.join(__dirname, 'check_target_mappings.log'); -async function removeLogFile() { - // ignore errors if it doesn't exist - await fs.unlink(logFilePath).catch(() => void 0); -} - -async function parseLogFile() { - const logFileContent = await fs.readFile(logFilePath, 'utf-8'); - - return logFileContent - .split('\n') - .filter(Boolean) - .map((str) => JSON5.parse(str)) as LogRecord[]; -} - -function logIncludes(logs: LogRecord[], message: string): boolean { - return Boolean(logs?.find((rec) => rec.message.includes(message))); -} - describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { let esServer: TestElasticsearchUtils; let root: Root; - let logs: LogRecord[]; + let logs: string; - beforeEach(async () => await removeLogFile()); + beforeEach(async () => { + await fs.unlink(logFilePath).catch(() => {}); + }); afterEach(async () => { await root?.shutdown(); @@ -71,9 +53,10 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(true); - expect(logIncludes(logs, 'CHECK_TARGET_MAPPINGS')).toEqual(false); + logs = await fs.readFile(logFilePath, 'utf-8'); + + expect(logs).toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_TARGET_MAPPINGS'); }); describe('when the indices are aligned with the stack version', () => { @@ -98,7 +81,7 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { // stop Kibana and remove logs await root.shutdown(); await delay(10); - await removeLogFile(); + await fs.unlink(logFilePath).catch(() => {}); root = createRoot(); await root.preboot(); @@ -106,14 +89,12 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(false); - expect( - logIncludes(logs, 'CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS') - ).toEqual(true); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS')).toEqual(false); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK')).toEqual(false); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_META')).toEqual(false); + logs = await fs.readFile(logFilePath, 'utf-8'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_META'); }); }); @@ -140,23 +121,13 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(false); - expect(logIncludes(logs, 'CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS')).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK') - ).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK -> UPDATE_TARGET_MAPPINGS_META') - ).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS') - ).toEqual(true); - expect( - logIncludes(logs, 'CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY') - ).toEqual(true); - expect(logIncludes(logs, 'MARK_VERSION_INDEX_READY -> DONE')).toEqual(true); - expect(logIncludes(logs, 'Migration completed')).toEqual(true); + logs = await fs.readFile(logFilePath, 'utf-8'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK -> UPDATE_TARGET_MAPPINGS_META'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).toMatch('Migration completed'); }); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts index 8df491c36f4e3..e1030fe9805e9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts @@ -10,122 +10,48 @@ import Path from 'path'; import Fs from 'fs'; import Util from 'util'; import JSON5 from 'json5'; +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { getMigrationDocLink, delay } from '../test_utils'; import { - createTestServers, - createRootWithCorePlugins, - type TestElasticsearchUtils, -} from '@kbn/core-test-helpers-kbn-server'; -import { Root } from '@kbn/core-root-server-internal'; -import { getMigrationDocLink } from '../test_utils'; + clearLog, + currentVersion, + defaultKibanaIndex, + getKibanaMigratorTestKit, + nextMinor, + startElasticsearch, +} from '../kibana_migrator_test_kit'; const migrationDocLink = getMigrationDocLink().resolveMigrationFailures; const logFilePath = Path.join(__dirname, 'cleanup.log'); -const asyncUnlink = Util.promisify(Fs.unlink); const asyncReadFile = Util.promisify(Fs.readFile); -async function removeLogFile() { - // ignore errors if it doesn't exist - await asyncUnlink(logFilePath).catch(() => void 0); -} - -function createRoot() { - return createRootWithCorePlugins( - { - migrations: { - skip: false, - }, - logging: { - appenders: { - file: { - type: 'file', - fileName: logFilePath, - layout: { - type: 'json', - }, - }, - }, - loggers: [ - { - name: 'root', - appenders: ['file'], - level: 'debug', // DEBUG logs are required to retrieve the PIT _id from the action response logs - }, - ], - }, - }, - { - oss: true, - } - ); -} - describe('migration v2', () => { - let esServer: TestElasticsearchUtils; - let root: Root; + let esServer: TestElasticsearchUtils['es']; + let esClient: ElasticsearchClient; beforeAll(async () => { - await removeLogFile(); + esServer = await startElasticsearch(); }); - afterAll(async () => { - if (root) { - await root.shutdown(); - } - if (esServer) { - await esServer.stop(); - } - - await new Promise((resolve) => setTimeout(resolve, 10000)); + beforeEach(async () => { + esClient = await setupBaseline(); + await clearLog(logFilePath); }); it('clean ups if migration fails', async () => { - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - // original SO: - // { - // _index: '.kibana_7.13.0_001', - // _type: '_doc', - // _id: 'index-pattern:test_index*', - // _version: 1, - // result: 'created', - // _shards: { total: 2, successful: 1, failed: 0 }, - // _seq_no: 0, - // _primary_term: 1 - // } - dataArchive: Path.join(__dirname, '..', 'archives', '7.13.0_with_corrupted_so.zip'), - }, - }, - }); - - root = createRoot(); - - esServer = await startES(); - await root.preboot(); - const coreSetup = await root.setup(); - - coreSetup.savedObjects.registerType({ - name: 'foo', - hidden: false, - mappings: { - properties: {}, - }, - namespaceType: 'agnostic', - migrations: { - '7.14.0': (doc) => doc, - }, - }); + const { migrator, client } = await setupNextMinor(); + migrator.prepareMigrations(); - await expect(root.start()).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 1 corrupt saved object documents were found: index-pattern:test_index* + await expect(migrator.runMigrations()).rejects.toThrowErrorMatchingInlineSnapshot(` + "Unable to complete saved object migrations for the [${defaultKibanaIndex}] index: Migrations failed. Reason: 1 corrupt saved object documents were found: corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60 - To allow migrations to proceed, please delete or fix these documents. - Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration. - Please refer to ${migrationDocLink} for more information." - `); + To allow migrations to proceed, please delete or fix these documents. + Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration. + Please refer to ${migrationDocLink} for more information." + `); const logFileContent = await asyncReadFile(logFilePath, 'utf-8'); const records = logFileContent @@ -134,7 +60,7 @@ describe('migration v2', () => { .map((str) => JSON5.parse(str)); const logRecordWithPit = records.find( - (rec) => rec.message === '[.kibana] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE' + (rec) => rec.message === `[${defaultKibanaIndex}] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE` ); expect(logRecordWithPit).toBeTruthy(); @@ -142,7 +68,6 @@ describe('migration v2', () => { const pitId = logRecordWithPit.right.pitId; expect(pitId).toBeTruthy(); - const client = esServer.es.getClient(); await expect( client.search({ body: { @@ -152,4 +77,132 @@ describe('migration v2', () => { // throws an exception that cannot search with closed PIT ).rejects.toThrow(/search_phase_execution_exception/); }); + + afterEach(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); }); + +const setupBaseline = async () => { + const typesCurrent: SavedObjectsType[] = [ + { + name: 'complex', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + }, + }, + migrations: {}, + }, + ]; + + const savedObjects = [ + { + id: 'complex:4baf4de0-a6d4-11ed-ba5a-39196fc76e60', + body: { + type: 'complex', + complex: { + name: 'foo', + value: 5, + }, + references: [], + coreMigrationVersion: currentVersion, + updated_at: '2023-02-07T11:04:44.914Z', + created_at: '2023-02-07T11:04:44.914Z', + }, + }, + { + id: 'corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60', // incorrect id => corrupt object + body: { + type: 'complex', + complex: { + name: 'bar', + value: 3, + }, + references: [], + coreMigrationVersion: currentVersion, + updated_at: '2023-02-07T11:04:44.914Z', + created_at: '2023-02-07T11:04:44.914Z', + }, + }, + ]; + + const { migrator: baselineMigrator, client } = await getKibanaMigratorTestKit({ + types: typesCurrent, + logFilePath, + }); + + baselineMigrator.prepareMigrations(); + await baselineMigrator.runMigrations(); + + // inject corrupt saved objects directly using esClient + await Promise.all( + savedObjects.map((savedObject) => { + client.create({ + index: defaultKibanaIndex, + refresh: 'wait_for', + ...savedObject, + }); + }) + ); + + return client; +}; + +const setupNextMinor = async () => { + const typesNextMinor: SavedObjectsType[] = [ + { + name: 'complex', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'keyword' }, + value: { type: 'long' }, + }, + }, + migrations: { + [nextMinor]: (doc) => doc, + }, + }, + ]; + + const { migrator, client } = await getKibanaMigratorTestKit({ + types: typesNextMinor, + kibanaVersion: nextMinor, + logFilePath, + settings: { + migrations: { + skip: false, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + level: 'debug', // DEBUG logs are required to retrieve the PIT _id from the action response logs + }, + ], + }, + }, + }); + + return { migrator, client }; +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts index f4577c0379096..51e3c7ce53fe0 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts @@ -112,7 +112,7 @@ describe('migration v2', () => { let rootB: Root; let rootC: Root; - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const fooType: SavedObjectsType = { name: 'foo', hidden: false, @@ -189,7 +189,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 0); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -208,7 +208,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 1); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -227,7 +227,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 5); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -246,7 +246,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 20); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts index 119ee16e9cddc..5c9ee13f3f825 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts @@ -46,7 +46,7 @@ describe('migration v2', () => { }); it('migrates the documents to the highest version', async () => { - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -90,7 +90,7 @@ describe('migration v2', () => { const coreStart = await root.start(); const esClient = coreStart.elasticsearch.client.asInternalUser; - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(1); const [doc] = migratedDocs; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index 691800feee0e3..64592be985719 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -34,7 +34,7 @@ import { type UpdateByQueryResponse, updateAndPickupMappings, type UpdateAndPickupMappingsResponse, - updateTargetMappingsMeta, + updateMappings, removeWriteBlock, transformDocs, waitForIndexStatus, @@ -71,7 +71,11 @@ describe('migration actions', () => { indexName: 'existing_index_with_docs', mappings: { dynamic: true, - properties: {}, + properties: { + someProperty: { + type: 'integer', + }, + }, _meta: { migrationMappingPropertyHashes: { references: '7997cf5a56cc02bdc9c93361bde732b0', @@ -1486,15 +1490,22 @@ describe('migration actions', () => { }); }); - describe('updateTargetMappingsMeta', () => { + describe('updateMappings', () => { it('rejects if ES throws an error', async () => { - const task = updateTargetMappingsMeta({ + const task = updateMappings({ client, index: 'no_such_index', - meta: { - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, }, }, })(); @@ -1502,13 +1513,51 @@ describe('migration actions', () => { await expect(task).rejects.toThrow('index_not_found_exception'); }); - it('resolves right when mappings._meta are correctly updated', async () => { - const res = await updateTargetMappingsMeta({ + it('resolves left when the mappings are incompatible', async () => { + const res = await updateMappings({ client, index: 'existing_index_with_docs', - meta: { - migrationMappingPropertyHashes: { - newReferences: 'fooBarHashMd509387420934879300d9', + mappings: { + properties: { + someProperty: { + type: 'date', // attempt to change an existing field's type in an incompatible fashion + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }, + }, + })(); + + expect(Either.isLeft(res)).toBe(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + + it('resolves right when mappings are correctly updated', async () => { + const res = await updateMappings({ + client, + index: 'existing_index_with_docs', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, }, }, })(); @@ -1519,8 +1568,17 @@ describe('migration actions', () => { index: ['existing_index_with_docs'], }); + expect(indices.existing_index_with_docs.mappings?.properties).toEqual( + expect.objectContaining({ + created_at: { + type: 'date', + }, + }) + ); + expect(indices.existing_index_with_docs.mappings?._meta).toEqual({ migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', newReferences: 'fooBarHashMd509387420934879300d9', }, }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts index 80681ffd0a5af..793ed9d100685 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts @@ -6,56 +6,28 @@ * Side Public License, v 1. */ -import Path from 'path'; -import fs from 'fs/promises'; -import { SemVer } from 'semver'; -import { Env } from '@kbn/config'; -import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { getEnvOptions } from '@kbn/config-mocks'; -import { REPO_ROOT } from '@kbn/repo-info'; -import { createTestServers, type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import { getKibanaMigratorTestKit } from '../kibana_migrator_test_kit'; -import { baselineDocuments, baselineTypes } from './active_delete.fixtures'; +import { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { + readLog, + clearLog, + nextMinor, + createBaseline, + currentVersion, + defaultKibanaIndex, + startElasticsearch, + getCompatibleMappingsMigrator, + getIdenticalMappingsMigrator, + getIncompatibleMappingsMigrator, + getNonDeprecatedMappingsMigrator, +} from '../kibana_migrator_test_kit'; import { delay } from '../test_utils'; -const kibanaIndex = '.kibana_migrator_tests'; -export const logFilePath = Path.join(__dirname, 'active_delete.test.log'); -const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; -const nextMinor = new SemVer(currentVersion).inc('minor').format(); - describe('when upgrading to a new stack version', () => { let esServer: TestElasticsearchUtils['es']; let esClient: ElasticsearchClient; - const startElasticsearch = async () => { - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - }, - }, - }); - return await startES(); - }; - - const createBaseline = async () => { - const { client, runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: baselineTypes, - }); - - await runMigrations(); - - await savedObjectsRepository.bulkCreate(baselineDocuments, { - refresh: 'wait_for', - }); - - return client; - }; - beforeAll(async () => { esServer = await startElasticsearch(); }); @@ -65,92 +37,66 @@ describe('when upgrading to a new stack version', () => { await delay(10); }); - describe('and the mappings match (diffMappings() === false)', () => { + describe('if the mappings match (diffMappings() === false)', () => { describe('and discardUnknownObjects = true', () => { let indexContents: SearchResponse<{ type: string }, Record>; beforeAll(async () => { esClient = await createBaseline(); - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); // remove the 'deprecated' type from the mappings, so that it is considered unknown - const types = baselineTypes.filter((type) => type.name !== 'deprecated'); - const { client, runMigrations } = await getKibanaMigratorTestKit({ + const { client, runMigrations } = await getNonDeprecatedMappingsMigrator({ settings: { migrations: { discardUnknownObjects: nextMinor, }, }, - kibanaIndex, - types, - kibanaVersion: nextMinor, - logFilePath, }); await runMigrations(); - indexContents = await client.search({ index: kibanaIndex, size: 100 }); + indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); it('the migrator is skipping reindex operation and executing CLEANUP_UNKNOWN_AND_EXCLUDED step', async () => { - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' - ); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED'); // we gotta inform that we are deleting unknown documents too (discardUnknownObjects: true) expect(logs).toMatch( - '[.kibana_migrator_tests] Kibana has been configured to discard unknown documents for this migration.' + 'Kibana has been configured to discard unknown documents for this migration.' ); - expect(logs).toMatch( 'Therefore, the following documents with unknown types will not be taken into account and they will not be available after the migration:' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' ); - expect(logs).toMatch('[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); }); describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { it('preserves documents with known types', async () => { - const basicDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'basic' - ).length; - - expect(basicDocumentCount).toEqual(3); + expect(countResultsByType(indexContents, 'basic')).toEqual(3); }); it('deletes documents with unknown types', async () => { - const deprecatedDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'deprecated' - ).length; - - expect(deprecatedDocumentCount).toEqual(0); + expect(countResultsByType(indexContents, 'deprecated')).toEqual(0); }); it('deletes documents that belong to REMOVED_TYPES', async () => { - const serverDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'server' - ).length; - - expect(serverDocumentCount).toEqual(0); + expect(countResultsByType(indexContents, 'server')).toEqual(0); }); it("deletes documents that have been excludeOnUpgrade'd via plugin hook", async () => { @@ -186,21 +132,15 @@ describe('when upgrading to a new stack version', () => { esClient = await createBaseline(); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); beforeEach(async () => { - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); }); it('fails if unknown documents exist', async () => { - // remove the 'deprecated' type from the mappings, so that SO of this type are considered unknown - const types = baselineTypes.filter((type) => type.name !== 'deprecated'); - const { runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types, - kibanaVersion: nextMinor, - logFilePath, - }); + // remove the 'deprecated' type from the mappings, so that it is considered unknown + const { runMigrations } = await getNonDeprecatedMappingsMigrator(); try { await runMigrations(); @@ -215,121 +155,237 @@ describe('when upgrading to a new stack version', () => { expect(errorMessage).toMatch(/deprecated:.*\(type: "deprecated"\)/); } - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL.'); + }); + + it('proceeds if there are no unknown documents', async () => { + const { client, runMigrations } = await getIdenticalMappingsMigrator(); + + await runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); - expect(logs).toMatch('[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + expect(indexContents.hits.hits.length).toEqual(8); }); + }); + }); - it('proceeds if there are no unknown documents', async () => { - const { client, runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: baselineTypes, - kibanaVersion: nextMinor, - logFilePath, + describe('if the mappings are compatible', () => { + describe('and discardUnknownObjects = true', () => { + let indexContents: SearchResponse<{ type: string }, Record>; + + beforeAll(async () => { + esClient = await createBaseline(); + + await clearLog(); + const { client, runMigrations } = await getCompatibleMappingsMigrator({ + filterDeprecated: true, // remove the 'deprecated' type from the mappings, so that it is considered unknown + settings: { + migrations: { + discardUnknownObjects: nextMinor, + }, + }, }); await runMigrations(); - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); + indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + }); + + afterAll(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + + it('the migrator is skipping reindex operation and executing CLEANUP_UNKNOWN_AND_EXCLUDED step', async () => { + const logs = await readLog(); + + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + // this step is run only if mappings are compatible but NOT equal + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + // we gotta inform that we are deleting unknown documents too (discardUnknownObjects: true), expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' + 'Kibana has been configured to discard unknown documents for this migration.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' + 'Therefore, the following documents with unknown types will not be taken into account and they will not be available after the migration:' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + }); + + describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { + it('preserves documents with known types', async () => { + expect(countResultsByType(indexContents, 'basic')).toEqual(3); + }); + + it('deletes documents with unknown types', async () => { + expect(countResultsByType(indexContents, 'deprecated')).toEqual(0); + }); + + it('deletes documents that belong to REMOVED_TYPES', async () => { + expect(countResultsByType(indexContents, 'server')).toEqual(0); + }); + + it("deletes documents that have been excludeOnUpgrade'd via plugin hook", async () => { + const complexDocuments = indexContents.hits.hits.filter( + (result) => result._source?.type === 'complex' + ); + + expect(complexDocuments.length).toEqual(2); + expect(complexDocuments[0]._source).toEqual( + expect.objectContaining({ + complex: { + name: 'complex-baz', + value: 2, + }, + type: 'complex', + }) + ); + expect(complexDocuments[1]._source).toEqual( + expect.objectContaining({ + complex: { + name: 'complex-lipsum', + value: 3, + }, + type: 'complex', + }) + ); + }); + }); + }); + + describe('and discardUnknownObjects = false', () => { + beforeAll(async () => { + esClient = await createBaseline(); + }); + afterAll(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + beforeEach(async () => { + await clearLog(); + }); + + it('fails if unknown documents exist', async () => { + const { runMigrations } = await getCompatibleMappingsMigrator({ + filterDeprecated: true, // remove the 'deprecated' type from the mappings, so that it is considered unknown + }); + + try { + await runMigrations(); + } catch (err) { + const errorMessage = err.message; + expect(errorMessage).toMatch( + 'Unable to complete saved object migrations for the [.kibana_migrator_tests] index: Migration failed because some documents were found which use unknown saved object types:' + ); + expect(errorMessage).toMatch( + 'To proceed with the migration you can configure Kibana to discard unknown saved objects for this migration.' + ); + expect(errorMessage).toMatch(/deprecated:.*\(type: "deprecated"\)/); + } + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); // this step is run only if mappings are compatible but NOT equal + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL.'); + }); + + it('proceeds if there are no unknown documents', async () => { + const { client, runMigrations } = await getCompatibleMappingsMigrator(); + + await runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); expect(logs).toMatch( - '[.kibana_migrator_tests] REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); - expect(logs).toMatch('[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); - const indexContents = await client.search({ index: kibanaIndex, size: 100 }); + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); expect(indexContents.hits.hits.length).toEqual(8); }); }); }); - describe('and the mappings do NOT match (diffMappings() === true)', () => { + describe('if the mappings do NOT match (diffMappings() === true) and they are NOT compatible', () => { beforeAll(async () => { esClient = await createBaseline(); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); beforeEach(async () => { - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); }); it('the migrator does not skip reindexing', async () => { - const incompatibleTypes: Array> = baselineTypes.map((type) => { - if (type.name === 'complex') { - return { - ...type, - mappings: { - properties: { - name: { type: 'keyword' }, // text => keyword - value: { type: 'long' }, // integer => long - }, - }, - }; - } else { - return type; - } - }); - - const { client, runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: incompatibleTypes, - kibanaVersion: nextMinor, - logFilePath, - }); + const { client, runMigrations } = await getIncompatibleMappingsMigrator(); await runMigrations(); - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.' - ); - expect(logs).toMatch('[.kibana_migrator_tests] MARK_VERSION_INDEX_READY -> DONE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch('CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.'); + expect(logs).toMatch('MARK_VERSION_INDEX_READY -> DONE'); const indexContents: SearchResponse< { type: string }, Record - > = await client.search({ index: kibanaIndex, size: 100 }); + > = await client.search({ index: defaultKibanaIndex, size: 100 }); expect(indexContents.hits.hits.length).toEqual(8); // we're removing a couple of 'complex' (value < = 1) // double-check that the deprecated documents have not been deleted - const deprecatedDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'deprecated' - ).length; - expect(deprecatedDocumentCount).toEqual(3); + expect(countResultsByType(indexContents, 'deprecated')).toEqual(3); }); }); }); + +const countResultsByType = ( + indexContents: SearchResponse<{ type: string }, Record>, + type: string +): number => { + return indexContents.hits.hits.filter((result) => result._source?.type === type).length; +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts index 5cb4028eba2ca..1240f5873e3a0 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts @@ -97,7 +97,7 @@ function createRoot({ logFileName, hosts }: RootConfig) { describe('migration v2', () => { let esServer: TestElasticsearchUtils; let root: Root; - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; beforeAll(async () => { await removeLogFile(); @@ -186,7 +186,7 @@ describe('migration v2', () => { await root.start(); const esClient = esServer.es.getClient(); - const migratedFooDocs = await fetchDocs(esClient, migratedIndex, 'foo'); + const migratedFooDocs = await fetchDocs(esClient, migratedIndexAlias, 'foo'); expect(migratedFooDocs.length).toBe(2500); migratedFooDocs.forEach((doc, i) => { expect(doc.id).toBe(`foo:${i}`); @@ -194,7 +194,7 @@ describe('migration v2', () => { expect(doc.migrationVersion.foo).toBe('7.14.0'); }); - const migratedBarDocs = await fetchDocs(esClient, migratedIndex, 'bar'); + const migratedBarDocs = await fetchDocs(esClient, migratedIndexAlias, 'bar'); expect(migratedBarDocs.length).toBe(2500); migratedBarDocs.forEach((doc, i) => { expect(doc.id).toBe(`bar:${i}`); 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 ae90b81482f4c..88193063d5526 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 @@ -113,7 +113,7 @@ describe('migration v2', () => { }); it('rewrites id deterministically for SO with namespaceType: "multiple" and "multiple-isolated"', async () => { - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -172,7 +172,7 @@ describe('migration v2', () => { const coreStart = await root.start(); const esClient = coreStart.elasticsearch.client.asInternalUser; - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); // each newly converted multi-namespace object in a non-default space has its ID deterministically regenerated, and a legacy-url-alias // object is created which links the old ID to the new ID diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts index aa5d1c0c06eb4..f239f36c9ebc4 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts @@ -5,121 +5,138 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import Path from 'path'; -import fs from 'fs/promises'; -import { Env } from '@kbn/config'; -import { getEnvOptions } from '@kbn/config-mocks'; -import { REPO_ROOT } from '@kbn/repo-info'; -import type { Root } from '@kbn/core-root-server-internal'; + +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; import { - createRootWithCorePlugins, - createTestServers, - type TestElasticsearchUtils, -} from '@kbn/core-test-helpers-kbn-server'; + readLog, + clearLog, + createBaseline, + currentVersion, + defaultKibanaIndex, + getCompatibleMappingsMigrator, + getIdenticalMappingsMigrator, + getIncompatibleMappingsMigrator, + startElasticsearch, +} from '../kibana_migrator_test_kit'; import { delay } from '../test_utils'; -import { SemVer } from 'semver'; - -const logFilePath = Path.join(__dirname, 'skip_reindex.log'); -describe('skip reindexing', () => { - const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; +// FLAKY: https://github.com/elastic/kibana/issues/152448 +describe.skip('when migrating to a new version', () => { let esServer: TestElasticsearchUtils['es']; - let root: Root; + let esClient: ElasticsearchClient; + let migrator: IKibanaMigrator; - afterEach(async () => { - await root?.shutdown(); - await esServer?.stop(); - await delay(10); + beforeAll(async () => { + esServer = await startElasticsearch(); + }); + + beforeEach(async () => { + esClient = await createBaseline(); + await clearLog(); }); - it('when migrating to a new version, but mappings remain the same', async () => { - let logs: string; - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - }, - }, + describe('and the mappings remain the same', () => { + it('the migrator skips reindexing', async () => { + // we run the migrator with the same identic baseline types + migrator = (await getIdenticalMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' + ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS'); + expect(logs).not.toMatch('REINDEX'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); }); - esServer = await startES(); - root = createRoot(); + }); - // Run initial migrations - await root.preboot(); - await root.setup(); - await root.start(); + describe("and the mappings' changes are still compatible", () => { + it('the migrator skips reindexing', async () => { + // we run the migrator with altered, compatible mappings + migrator = (await getCompatibleMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); - // stop Kibana and remove logs - await root.shutdown(); - await delay(10); - await fs.unlink(logFilePath).catch(() => {}); - - const nextPatch = new SemVer(currentVersion).inc('patch').format(); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); - - logs = await fs.readFile(logFilePath, 'utf-8'); - - expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED'); - expect(logs).toMatch( - 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' - ); - expect(logs).toMatch( - 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' - ); - expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); - expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' + ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); - expect(logs).not.toMatch('CREATE_NEW_TARGET'); - expect(logs).not.toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS'); + expect(logs).not.toMatch('REINDEX'); + }); + }); - // We restart Kibana again after doing a "compatible migration" to ensure that - // the next time state is loaded everything still works as expected. - // For instance, we might see something like: - // Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Unexpected Elasticsearch ResponseError: statusCode: 404, method: POST, url: /.kibana_8.7.1_001/_pit?keep_alive=10m error: [index_not_found_exception]: no such index [.kibana_8.7.1_001] - await root.shutdown(); - await delay(10); - await fs.unlink(logFilePath).catch(() => {}); + describe("and the mappings' changes are NOT compatible", () => { + it('the migrator reindexes documents to a new index', async () => { + // we run the migrator with altered, compatible mappings + migrator = (await getIncompatibleMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.'); + expect(logs).toMatch('MARK_VERSION_INDEX_READY -> DONE.'); - logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - expect(logs).not.toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED'); + expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION'); + }); + }); + + afterEach(async () => { + // we run the migrator again to ensure that the next time state is loaded everything still works as expected + await clearLog(); + await migrator.runMigrations({ rerun: true }); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + expect(logs).not.toMatch('WAIT_FOR_YELLOW_SOURCE'); + expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUCED'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); + + // clear the system index for next test + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); -}); -function createRoot(kibanaVersion?: string): Root { - return createRootWithCorePlugins( - { - logging: { - appenders: { - file: { - type: 'file', - fileName: logFilePath, - layout: { - type: 'json', - }, - }, - }, - loggers: [ - { - name: 'root', - level: 'info', - appenders: ['file'], - }, - ], - }, - }, - { oss: true }, - kibanaVersion - ); -} + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.ts new file mode 100644 index 0000000000000..7d605cf116341 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.ts @@ -0,0 +1,82 @@ +/* + * 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 { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; + +const defaultType: SavedObjectsType = { + name: 'defaultType', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'keyword' }, + }, + }, + migrations: {}, +}; + +export const baselineTypes: Array> = [ + { + ...defaultType, + name: 'server', + }, + { + ...defaultType, + name: 'basic', + }, + { + ...defaultType, + name: 'deprecated', + }, + { + ...defaultType, + name: 'complex', + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + }, + }, + excludeOnUpgrade: () => { + return { + bool: { + must: [{ term: { type: 'complex' } }, { range: { 'complex.value': { lte: 1 } } }], + }, + }; + }, + }, +]; + +export const baselineDocuments: SavedObjectsBulkCreateObject[] = [ + ...['server-foo', 'server-bar', 'server-baz'].map((name) => ({ + type: 'server', + attributes: { + name, + }, + })), + ...['basic-foo', 'basic-bar', 'basic-baz'].map((name) => ({ + type: 'basic', + attributes: { + name, + }, + })), + ...['deprecated-foo', 'deprecated-bar', 'deprecated-baz'].map((name) => ({ + type: 'deprecated', + attributes: { + name, + }, + })), + ...['complex-foo', 'complex-bar', 'complex-baz', 'complex-lipsum'].map((name, index) => ({ + type: 'complex', + attributes: { + name, + value: index, + }, + })), +]; diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index df3cce7dbdca6..346271affabf2 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -7,6 +7,9 @@ */ import Path from 'path'; +import fs from 'fs/promises'; +import { SemVer } from 'semver'; + import { defaultsDeep } from 'lodash'; import { BehaviorSubject, firstValueFrom, map } from 'rxjs'; import { ConfigService, Env } from '@kbn/config'; @@ -19,8 +22,8 @@ import { type SavedObjectsConfigType, type SavedObjectsMigrationConfigType, SavedObjectTypeRegistry, - IKibanaMigrator, - MigrationResult, + type IKibanaMigrator, + type MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; import { @@ -32,20 +35,23 @@ import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server- import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { esTestConfig, kibanaServerTestUser } from '@kbn/test'; -import { LoggerFactory } from '@kbn/logging'; +import type { LoggerFactory } from '@kbn/logging'; +import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { registerServiceConfig } from '@kbn/core-root-server-internal'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; import { getDocLinks, getDocLinksMeta } from '@kbn/doc-links'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { baselineDocuments, baselineTypes } from './kibana_migrator_test_kit.fixtures'; export const defaultLogFilePath = Path.join(__dirname, 'kibana_migrator_test_kit.log'); const env = Env.createDefault(REPO_ROOT, getEnvOptions()); // Extract current stack version from Env, to use as a default -const currentVersion = env.packageInfo.version; -const currentBranch = env.packageInfo.branch; +export const currentVersion = env.packageInfo.version; +export const nextMinor = new SemVer(currentVersion).inc('minor').format(); +export const currentBranch = env.packageInfo.branch; +export const defaultKibanaIndex = '.kibana_migrator_tests'; export interface GetEsClientParams { settings?: Record; @@ -76,7 +82,7 @@ export const startElasticsearch = async ({ }: { basePath?: string; dataArchive?: string; -}) => { +} = {}) => { const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -109,7 +115,7 @@ export const getEsClient = async ({ export const getKibanaMigratorTestKit = async ({ settings = {}, - kibanaIndex = '.kibana', + kibanaIndex = defaultKibanaIndex, kibanaVersion = currentVersion, kibanaBranch = currentBranch, types = [], @@ -227,7 +233,9 @@ const getElasticsearchClient = async ( return configureClient(esClientConfig, { logger: loggerFactory.get('elasticsearch'), type: 'data', - agentFactoryProvider: new AgentManager(), + agentFactoryProvider: new AgentManager( + loggerFactory.get('elasticsearch-service', 'agent-manager') + ), kibanaVersion, }); }; @@ -272,3 +280,113 @@ const registerTypes = ( ) => { (types || []).forEach((type) => typeRegistry.registerType(type)); }; + +export const createBaseline = async () => { + const { client, migrator, savedObjectsRepository } = await getKibanaMigratorTestKit({ + kibanaIndex: defaultKibanaIndex, + types: baselineTypes, + }); + + migrator.prepareMigrations(); + await migrator.runMigrations(); + + await savedObjectsRepository.bulkCreate(baselineDocuments, { + refresh: 'wait_for', + }); + + return client; +}; + +interface GetMutatedMigratorParams { + kibanaVersion?: string; + settings?: Record; +} + +export const getIdenticalMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + return await getKibanaMigratorTestKit({ + types: baselineTypes, + kibanaVersion, + settings, + }); +}; + +export const getNonDeprecatedMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + return await getKibanaMigratorTestKit({ + types: baselineTypes.filter((type) => type.name !== 'deprecated'), + kibanaVersion, + settings, + }); +}; + +export const getCompatibleMappingsMigrator = async ({ + filterDeprecated = false, + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams & { filterDeprecated?: boolean } = {}) => { + const types = baselineTypes + .filter((type) => !filterDeprecated || type.name !== 'deprecated') + .map((type) => { + if (type.name === 'complex') { + return { + ...type, + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + createdAt: { type: 'date' }, + }, + }, + }; + } else { + return type; + } + }); + + return await getKibanaMigratorTestKit({ + types, + kibanaVersion, + settings, + }); +}; + +export const getIncompatibleMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + const types = baselineTypes.map((type) => { + if (type.name === 'complex') { + return { + ...type, + mappings: { + properties: { + name: { type: 'keyword' }, + value: { type: 'long' }, + createdAt: { type: 'date' }, + }, + }, + }; + } else { + return type; + } + }); + + return await getKibanaMigratorTestKit({ + types, + kibanaVersion, + settings, + }); +}; + +export const readLog = async (logFilePath: string = defaultLogFilePath): Promise => { + return await fs.readFile(logFilePath, 'utf-8'); +}; + +export const clearLog = async (logFilePath: string = defaultLogFilePath): Promise => { + await fs.truncate(logFilePath).catch(() => {}); +}; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts index f8e411fabbb55..7cb07fbd31ea7 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts @@ -500,7 +500,11 @@ export class LegacyCoreEditor implements CoreEditor { addFoldsAtRanges(foldRanges: Range[]) { const session = this.editor.getSession(); foldRanges.forEach((range) => { - session.addFold('...', _AceRange.fromPoints(range.start, range.end)); + try { + session.addFold('...', _AceRange.fromPoints(range.start, range.end)); + } catch (e) { + // ignore the error if a fold fails + } }); } } diff --git a/src/plugins/dashboard/kibana.jsonc b/src/plugins/dashboard/kibana.jsonc index ae5194d662c2b..aba26ca66ed7d 100644 --- a/src/plugins/dashboard/kibana.jsonc +++ b/src/plugins/dashboard/kibana.jsonc @@ -16,6 +16,8 @@ "inspector", "navigation", "savedObjects", + "savedObjectsFinder", + "savedObjectsManagement", "share", "screenshotMode", "uiActions", diff --git a/src/plugins/dashboard/public/dashboard_actions/index.ts b/src/plugins/dashboard/public/dashboard_actions/index.ts index 652b66b2ad195..199854710f1f2 100644 --- a/src/plugins/dashboard/public/dashboard_actions/index.ts +++ b/src/plugins/dashboard/public/dashboard_actions/index.ts @@ -8,7 +8,7 @@ import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public'; import { CoreStart } from '@kbn/core/public'; -import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { ExportCSVAction } from './export_csv_action'; import { ClonePanelAction } from './clone_panel_action'; @@ -33,13 +33,13 @@ export const buildAllDashboardActions = async ({ allowByValueEmbeddables, }: BuildAllDashboardActionsProps) => { const { uiSettings } = core; - const { uiActions, share, presentationUtil } = plugins; + const { uiActions, share, presentationUtil, savedObjectsManagement } = plugins; const clonePanelAction = new ClonePanelAction(core.savedObjects); uiActions.registerAction(clonePanelAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); - const SavedObjectFinder = getSavedObjectFinder(uiSettings, core.http); + const SavedObjectFinder = getSavedObjectFinder(uiSettings, core.http, savedObjectsManagement); const changeViewAction = new ReplacePanelAction(SavedObjectFinder); uiActions.registerAction(changeViewAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); diff --git a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts index 6d90316098fb5..cbfc7271aef41 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts @@ -37,28 +37,35 @@ export const isPanelVersionTooOld = (panels: SavedDashboardPanel[]) => { return false; }; +function getPanelsMap(appStateInUrl: SharedDashboardState): DashboardPanelMap | undefined { + if (!appStateInUrl.panels) { + return undefined; + } + + if (appStateInUrl.panels.length === 0) { + return {}; + } + + if (isPanelVersionTooOld(appStateInUrl.panels)) { + pluginServices.getServices().notifications.toasts.addWarning(getPanelTooOldErrorString()); + return undefined; + } + + return convertSavedPanelsToPanelMap(appStateInUrl.panels); +} + /** * Loads any dashboard state from the URL, and removes the state from the URL. */ export const loadAndRemoveDashboardState = ( kbnUrlStateStorage: IKbnUrlStateStorage ): Partial => { - const { - notifications: { toasts }, - } = pluginServices.getServices(); const rawAppStateInUrl = kbnUrlStateStorage.get( DASHBOARD_STATE_STORAGE_KEY ); if (!rawAppStateInUrl) return {}; - let panelsMap: DashboardPanelMap | undefined; - if (rawAppStateInUrl.panels && rawAppStateInUrl.panels.length > 0) { - if (isPanelVersionTooOld(rawAppStateInUrl.panels)) { - toasts.addWarning(getPanelTooOldErrorString()); - } else { - panelsMap = convertSavedPanelsToPanelMap(rawAppStateInUrl.panels); - } - } + const panelsMap = getPanelsMap(rawAppStateInUrl); const nextUrl = replaceUrlHashQuery(window.location.href, (hashQuery) => { delete hashQuery[DASHBOARD_STATE_STORAGE_KEY]; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts index 79a3caeafba17..06389afc2a576 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts @@ -8,7 +8,7 @@ import { HttpStart } from '@kbn/core/public'; import { isErrorEmbeddable, openAddPanelFlyout } from '@kbn/embeddable-plugin/public'; -import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardContainer } from '../dashboard_container'; @@ -21,12 +21,17 @@ export function addFromLibrary(this: DashboardContainer) { settings: { uiSettings, theme }, embeddable: { getEmbeddableFactories, getEmbeddableFactory }, http, + savedObjectsManagement, } = pluginServices.getServices(); if (isErrorEmbeddable(this)) return; this.openOverlay( openAddPanelFlyout({ - SavedObjectFinder: getSavedObjectFinder(uiSettings, http as HttpStart), + SavedObjectFinder: getSavedObjectFinder( + uiSettings, + http as HttpStart, + savedObjectsManagement + ), reportUiCounter: usageCollection.reportUiCounter, getAllFactories: getEmbeddableFactories, getFactory: getEmbeddableFactory, diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts index c4886c55d976c..74f852df484e4 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts @@ -15,6 +15,7 @@ import { isFilterPinned, onlyDisabledFiltersChanged, } from '@kbn/es-query'; +import { shouldRefreshFilterCompareOptions } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../../dashboard_container'; import { DashboardContainerByValueInput } from '../../../../../common'; @@ -117,12 +118,6 @@ export const unsavedChangesDiffingFunctions: DashboardDiffFunctions = { viewMode: () => false, // When compared view mode is always considered unequal so that it gets backed up. }; -const shouldRefreshFilterCompareOptions = { - ...COMPARE_ALL_OPTIONS, - // do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results) - state: false, -}; - export const shouldRefreshDiffingFunctions: DashboardDiffFunctions = { ...unsavedChangesDiffingFunctions, filters: ({ currentValue, lastValue }) => diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index cb13afd4aabf7..3c4031072ca92 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -50,6 +50,7 @@ import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { DashboardContainerFactoryDefinition } from './dashboard_container/embeddable/dashboard_container_factory'; import { type DashboardAppLocator, @@ -90,6 +91,7 @@ export interface DashboardStartDependencies { presentationUtil: PresentationUtilPluginStart; savedObjects: SavedObjectsStart; savedObjectsClient: SavedObjectsClientContract; + savedObjectsManagement: SavedObjectsManagementPluginStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; screenshotMode: ScreenshotModePluginStart; share?: SharePluginStart; diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts index eabe85288687a..f0ec4a91f3fd6 100644 --- a/src/plugins/dashboard/public/services/plugin_services.stub.ts +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -39,6 +39,7 @@ import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub'; import { customBrandingServiceFactory } from './custom_branding/custom_branding.stub'; +import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service.stub'; export const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory), @@ -66,6 +67,7 @@ export const providers: PluginServiceProviders = { usageCollection: new PluginServiceProvider(usageCollectionServiceFactory), visualizations: new PluginServiceProvider(visualizationsServiceFactory), customBranding: new PluginServiceProvider(customBrandingServiceFactory), + savedObjectsManagement: new PluginServiceProvider(savedObjectsManagementServiceFactory), }; export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts index 4382506a37948..599eb6a5b7249 100644 --- a/src/plugins/dashboard/public/services/plugin_services.ts +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -40,6 +40,7 @@ import { usageCollectionServiceFactory } from './usage_collection/usage_collecti import { analyticsServiceFactory } from './analytics/analytics_service'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service'; import { customBrandingServiceFactory } from './custom_branding/custom_branding_service'; +import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service'; const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [ @@ -80,6 +81,7 @@ const providers: PluginServiceProviders(); diff --git a/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.ts b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.ts new file mode 100644 index 0000000000000..b5ea444e702a8 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.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 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 { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-plugin/public/mocks'; + +type SavedObjectsManagementServiceFactory = PluginServiceFactory; + +export const savedObjectsManagementServiceFactory: SavedObjectsManagementServiceFactory = () => { + return savedObjectsManagementPluginMock.createStartContract(); +}; diff --git a/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.ts b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.ts new file mode 100644 index 0000000000000..271a056948687 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.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 { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { DashboardStartDependencies } from '../../plugin'; + +export type SavedObjectsManagementServiceFactory = KibanaPluginServiceFactory< + SavedObjectsManagementPluginStart, + DashboardStartDependencies +>; + +export const savedObjectsManagementServiceFactory: SavedObjectsManagementServiceFactory = ({ + startPlugins, +}) => { + const { savedObjectsManagement } = startPlugins; + + return savedObjectsManagement; +}; diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts index fc7e0acf1b5c4..1992cd83d4b6d 100644 --- a/src/plugins/dashboard/public/services/types.ts +++ b/src/plugins/dashboard/public/services/types.ts @@ -8,6 +8,7 @@ import { PluginInitializerContext } from '@kbn/core/public'; import { KibanaPluginServiceParams } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { DashboardStartDependencies } from '../plugin'; import { DashboardAnalyticsService } from './analytics/types'; @@ -66,4 +67,5 @@ export interface DashboardServices { usageCollection: DashboardUsageCollectionService; // TODO: make this optional in follow up visualizations: DashboardVisualizationsService; customBranding: DashboardCustomBrandingService; + savedObjectsManagement: SavedObjectsManagementPluginStart; } diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 17ec86d25e6ef..4bfb899c5301d 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -53,6 +53,8 @@ "@kbn/core-execution-context-common", "@kbn/core-custom-branding-browser", "@kbn/shared-ux-router", + "@kbn/saved-objects-finder-plugin", + "@kbn/saved-objects-management-plugin", "@kbn/shared-ux-button-toolbar", ], "exclude": [ diff --git a/src/plugins/data_view_editor/kibana.jsonc b/src/plugins/data_view_editor/kibana.jsonc index 007a7f78321db..bdec3b4f4943d 100644 --- a/src/plugins/data_view_editor/kibana.jsonc +++ b/src/plugins/data_view_editor/kibana.jsonc @@ -13,6 +13,7 @@ ], "requiredBundles": [ "kibanaReact", + "kibanaUtils", "esUiShared" ] } diff --git a/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx b/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx new file mode 100644 index 0000000000000..37d8f53b69ab9 --- /dev/null +++ b/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 { findTestSubject } from '@elastic/eui/lib/test'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { TitleDocsPopover } from './title_docs_popover'; + +describe('DataViewEditor TitleDocsPopover', () => { + it('should render normally', async () => { + const component = mountWithIntl(); + + expect(findTestSubject(component, 'indexPatternDocsButton').exists()).toBeTruthy(); + expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeFalsy(); + + findTestSubject(component, 'indexPatternDocsButton').simulate('click'); + + await component.update(); + + expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeTruthy(); + }); +}); diff --git a/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.tsx b/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.tsx new file mode 100644 index 0000000000000..7e762a161639e --- /dev/null +++ b/src/plugins/data_view_editor/public/components/form_fields/title_docs_popover.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { useState } from 'react'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButtonIcon, + EuiPanel, + EuiPopover, + EuiPopoverTitle, + EuiText, + EuiCode, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const TitleDocsPopover: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + + const helpButton = ( + setIsOpen((prev) => !prev)} + iconType="documentation" + data-test-subj="indexPatternDocsButton" + aria-label={i18n.translate('indexPatternEditor.titleDocsPopover.ariaLabel', { + defaultMessage: 'Index pattern examples', + })} + /> + ); + + return ( + setIsOpen(false)} + > + + {i18n.translate('indexPatternEditor.titleDocsPopover.title', { + defaultMessage: 'Index pattern', + })} + + + +

    + +

    +
      +
    • +

      + +

      +

      + filebeat-* +

      +
    • +
    • +

      + +

      +

      + filebeat-a,filebeat-b +

      +
    • +
    • +

      + +

      +

      + filebeat-*,-filebeat-c +

      +
    • +
    • +

      + {i18n.translate( + 'indexPatternEditor.titleDocsPopover.dontUseSpecialCharactersDescription', + { + defaultMessage: 'Spaces and the characters /?"<>| are not allowed.', + } + )} +

      +
    • +
    +
    +
    +
    + ); +}; diff --git a/src/plugins/data_view_editor/public/components/form_fields/title_field.tsx b/src/plugins/data_view_editor/public/components/form_fields/title_field.tsx index b2ea9c78e9fca..3824a6cea5258 100644 --- a/src/plugins/data_view_editor/public/components/form_fields/title_field.tsx +++ b/src/plugins/data_view_editor/public/components/form_fields/title_field.tsx @@ -22,6 +22,7 @@ import { canAppendWildcard } from '../../lib'; import { schema } from '../form_schema'; import { RollupIndicesCapsResponse, IndexPatternConfig, MatchedIndicesSet } from '../../types'; import { matchedIndiciesDefault } from '../../data_view_editor_service'; +import { TitleDocsPopover } from './title_docs_popover'; interface TitleFieldProps { isRollup: boolean; @@ -194,6 +195,8 @@ export const TitleField = ({ isLoading={field.isValidating} fullWidth data-test-subj="createIndexPatternTitleInput" + append={} + placeholder="example-*" /> ); diff --git a/src/plugins/data_view_editor/public/components/form_schema.ts b/src/plugins/data_view_editor/public/components/form_schema.ts index 69993f17ecb35..eeadf52075440 100644 --- a/src/plugins/data_view_editor/public/components/form_schema.ts +++ b/src/plugins/data_view_editor/public/components/form_schema.ts @@ -28,10 +28,6 @@ export const schema = { defaultMessage: 'Index pattern', }), defaultValue: '', - helpText: i18n.translate('indexPatternEditor.validations.titleHelpText', { - defaultMessage: - 'Enter an index pattern that matches one or more data sources. Use an asterisk (*) to match multiple characters. Spaces and the characters , /, ?, ", <, >, | are not allowed.', - }), validations: [ { validator: fieldValidators.emptyField( @@ -84,7 +80,7 @@ export const schema = { }, isAdHoc: { label: i18n.translate('indexPatternEditor.editor.form.IsAdHocLabel', { - defaultMessage: 'Creeate AdHoc DataView', + defaultMessage: 'Create AdHoc DataView', }), defaultValue: false, type: 'hidden', diff --git a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap index 609d88fac17b6..c068a7ef09447 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap +++ b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap @@ -201,6 +201,113 @@ exports[`IndicesList should change per page 1`] = `
    `; +exports[`IndicesList should highlight fully when an exact match 1`] = ` +
    + + + + + + + logs + + tash + + + + + + + + some_logs + + + + + + + + + + + + + } + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="customizablePagination" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + + 5 + , + + 10 + , + + 20 + , + + 50 + , + ] + } + /> + + + +
    +`; + exports[`IndicesList should highlight the query in the matches 1`] = `
    - es + + es + diff --git a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.test.tsx b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.test.tsx index 074865006a385..54b996416c223 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.test.tsx +++ b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.test.tsx @@ -7,24 +7,39 @@ */ import React from 'react'; -import { IndicesList } from '.'; +import { IndicesList, IndicesListProps, PER_PAGE_STORAGE_KEY } from './indices_list'; import { shallow } from 'enzyme'; import { MatchedItem } from '@kbn/data-views-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; const indices = [ { name: 'kibana', tags: [] }, { name: 'es', tags: [] }, ] as unknown as MatchedItem[]; +const similarIndices = [ + { name: 'logstash', tags: [] }, + { name: 'some_logs', tags: [] }, +] as unknown as MatchedItem[]; + describe('IndicesList', () => { + const commonProps: Omit = { + indices, + isExactMatch: jest.fn(() => false), + }; + + afterEach(() => { + new Storage(localStorage).remove(PER_PAGE_STORAGE_KEY); + }); + it('should render normally', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); it('should change pages', () => { - const component = shallow(); + const component = shallow(); const instance = component.instance() as IndicesList; @@ -36,7 +51,7 @@ describe('IndicesList', () => { }); it('should change per page', () => { - const component = shallow(); + const component = shallow(); const instance = component.instance() as IndicesList; instance.onChangePerPage(1); @@ -46,14 +61,33 @@ describe('IndicesList', () => { }); it('should highlight the query in the matches', () => { - const component = shallow(); + const component = shallow( + indexName === 'es'} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + it('should highlight fully when an exact match', () => { + const component = shallow( + indexName === 'some_logs'} + /> + ); expect(component).toMatchSnapshot(); }); describe('updating props', () => { it('should render all new indices', () => { - const component = shallow(); + const component = shallow(); const moreIndices = [ ...indices, diff --git a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx index f307bbfc43889..d7542a9e70184 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx +++ b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx @@ -25,13 +25,14 @@ import { } from '@elastic/eui'; import { Pager } from '@elastic/eui'; - +import { Storage } from '@kbn/kibana-utils-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { MatchedItem, Tag } from '@kbn/data-views-plugin/public'; -interface IndicesListProps { +export interface IndicesListProps { indices: MatchedItem[]; query: string; + isExactMatch: (indexName: string) => boolean; } interface IndicesListState { @@ -41,15 +42,20 @@ interface IndicesListState { } const PER_PAGE_INCREMENTS = [5, 10, 20, 50]; +export const PER_PAGE_STORAGE_KEY = 'dataViews.previewPanel.indicesPerPage'; export class IndicesList extends React.Component { pager: Pager; + storage: Storage; + constructor(props: IndicesListProps) { super(props); + this.storage = new Storage(localStorage); + this.state = { page: 0, - perPage: PER_PAGE_INCREMENTS[1], + perPage: this.storage.get(PER_PAGE_STORAGE_KEY) || PER_PAGE_INCREMENTS[1], isPerPageControlOpen: false, }; @@ -75,6 +81,7 @@ export class IndicesList extends React.Component { @@ -144,11 +151,20 @@ export class IndicesList extends React.Component q.trim()); + if (isExactMatch(indexName)) { + return {indexName}; + } + + const queryAsArray = query + .split(',') + .map((q) => q.trim()) + .filter(Boolean); let queryIdx = -1; let queryWithoutWildcard = ''; for (let i = 0; i < queryAsArray.length; i++) { @@ -162,6 +178,7 @@ export class IndicesList extends React.Component { diff --git a/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.test.tsx b/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.test.tsx new file mode 100644 index 0000000000000..66ba61b466a62 --- /dev/null +++ b/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.test.tsx @@ -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 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 { findTestSubject } from '@elastic/eui/lib/test'; +import { EuiTable, EuiButtonGroup } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { INDEX_PATTERN_TYPE, MatchedItem } from '@kbn/data-views-plugin/public'; +import { Props as PreviewPanelProps, PreviewPanel } from './preview_panel'; +import { from } from 'rxjs'; + +const indices = [ + { name: 'kibana_1', tags: [] }, + { name: 'kibana_2', tags: [] }, + { name: 'es', tags: [] }, +] as unknown as MatchedItem[]; + +describe('DataViewEditor PreviewPanel', () => { + const commonProps: Omit = { + type: INDEX_PATTERN_TYPE.DEFAULT, + allowHidden: false, + }; + + it('should render normally by default', async () => { + const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ + { + allIndices: indices, + exactMatchedIndices: [], + partialMatchedIndices: [], + visibleIndices: indices, + }, + ]); + const component = await mountWithIntl( + + ); + + expect(component.find(EuiTable).exists()).toBeTruthy(); + expect(component.find(EuiButtonGroup).exists()).toBeFalsy(); + }); + + it('should render matching indices and can switch to all indices', async () => { + const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ + { + allIndices: indices, + exactMatchedIndices: [indices[0], indices[1]], + partialMatchedIndices: [], + visibleIndices: [indices[0], indices[1]], + }, + ]); + const component = await mountWithIntl( + + ); + + expect(component.find(EuiTable).exists()).toBeTruthy(); + expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); + + expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe( + 'Matching sources' + ); + + findTestSubject(component, 'allIndices').simulate('change', { + target: { + value: true, + }, + }); + + await component.update(); + + expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources'); + }); + + it('should render matching indices with warnings', async () => { + const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ + { + allIndices: indices, + exactMatchedIndices: [], + partialMatchedIndices: [indices[0], indices[1]], + visibleIndices: [indices[0], indices[1]], + }, + ]); + const component = await mountWithIntl( + + ); + + expect(component.find(EuiTable).exists()).toBeTruthy(); + expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); + }); + + it('should render all indices tab when ends with a comma and can switch to matching sources', async () => { + const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ + { + allIndices: indices, + exactMatchedIndices: [indices[0]], + partialMatchedIndices: [], + visibleIndices: [indices[0]], + }, + ]); + const component = await mountWithIntl( + + ); + + expect(component.find(EuiTable).exists()).toBeTruthy(); + expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); + + expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources'); + + findTestSubject(component, 'onlyMatchingIndices').simulate('change', { + target: { + value: true, + }, + }); + + await component.update(); + + expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe( + 'Matching sources' + ); + }); +}); diff --git a/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.tsx b/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.tsx index 07b1fd91b85b6..629d2ae8bde46 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.tsx +++ b/src/plugins/data_view_editor/public/components/preview_panel/preview_panel.tsx @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import React from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import React, { useState } from 'react'; +import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public'; @@ -17,7 +18,27 @@ import { matchedIndiciesDefault } from '../../data_view_editor_service'; import { MatchedIndicesSet } from '../../types'; -interface Props { +enum ViewMode { + allIndices = 'allIndices', + onlyMatchingIndices = 'onlyMatchingIndices', +} + +const viewModeButtons = [ + { + id: ViewMode.allIndices, + label: i18n.translate('indexPatternEditor.previewPanel.viewModeGroup.allSourcesButton', { + defaultMessage: 'All sources', + }), + }, + { + id: ViewMode.onlyMatchingIndices, + label: i18n.translate('indexPatternEditor.previewPanel.viewModeGroup.matchingSourcesButton', { + defaultMessage: 'Matching sources', + }), + }, +]; + +export interface Props { type: INDEX_PATTERN_TYPE; allowHidden: boolean; title: string; @@ -25,20 +46,35 @@ interface Props { } export const PreviewPanel = ({ type, allowHidden, title = '', matchedIndices$ }: Props) => { + const [viewMode, setViewMode] = useState(); const matched = useObservable(matchedIndices$, matchedIndiciesDefault); - const indicesListContent = - matched.visibleIndices.length || matched.allIndices.length ? ( - <> - - - - ) : ( - <> - ); + + let currentlyVisibleIndices; + let currentViewMode; + + if ( + (title.length && !isAboutToIncludeMoreIndices(title) && viewMode !== ViewMode.allIndices) || + viewMode === ViewMode.onlyMatchingIndices + ) { + currentlyVisibleIndices = matched.visibleIndices; + currentViewMode = ViewMode.onlyMatchingIndices; + } else { + currentlyVisibleIndices = matched.allIndices; + currentViewMode = ViewMode.allIndices; + } + + const indicesListContent = currentlyVisibleIndices.length ? ( + + title.length > 0 && matched.exactMatchedIndices.some((index) => index.name === indexName) + } + /> + ) : ( + <> + ); return ( <> @@ -48,7 +84,23 @@ export const PreviewPanel = ({ type, allowHidden, title = '', matchedIndices$ }: isIncludingSystemIndices={allowHidden} query={title} /> + + {Boolean(title) && currentlyVisibleIndices.length > 0 && ( + setViewMode(id as ViewMode)} + /> + )} {indicesListContent} ); }; + +function isAboutToIncludeMoreIndices(query: string) { + return query.trimEnd().endsWith(','); +} diff --git a/src/plugins/data_view_editor/tsconfig.json b/src/plugins/data_view_editor/tsconfig.json index 3f1744281db90..99e066ee3fe66 100644 --- a/src/plugins/data_view_editor/tsconfig.json +++ b/src/plugins/data_view_editor/tsconfig.json @@ -17,6 +17,7 @@ "@kbn/i18n", "@kbn/test-jest-helpers", "@kbn/ui-theme", + "@kbn/kibana-utils-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/data_views/common/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts index 01e2b9b1d95f3..e5e9b2d83faf7 100644 --- a/src/plugins/data_views/common/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -19,10 +19,11 @@ import { IDataViewsApiClient, } from '../types'; import { stubbedSavedObjectIndexPattern } from '../data_view.stub'; +import { DataViewMissingIndices } from '../lib'; const createFieldsFetcher = () => ({ - getFieldsForWildcard: jest.fn(async () => ({ fields: [], indices: [] })), + getFieldsForWildcard: jest.fn(async () => ({ fields: [], indices: ['test'] })), } as any as IDataViewsApiClient); const fieldFormats = fieldFormatsMock; @@ -71,6 +72,7 @@ describe('IndexPatterns', () => { const indexPatternObj = { id: 'id', version: 'a', attributes: { title: 'title' } }; beforeEach(() => { + jest.clearAllMocks(); savedObjectsClient = {} as SavedObjectsClientCommon; savedObjectsClient.find = jest.fn( () => Promise.resolve([indexPatternObj]) as Promise>> @@ -220,6 +222,35 @@ describe('IndexPatterns', () => { expect((await indexPatterns.get(id)).fields.length).toBe(1); }); + test('existing indices, so dataView.matchedIndices.length equals 1 ', async () => { + const id = '1'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + const dataView = await indexPatterns.get(id); + expect(dataView.matchedIndices.length).toBe(1); + }); + + test('missing indices, so dataView.matchedIndices.length equals 0 ', async () => { + const id = '1'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + apiClient.getFieldsForWildcard = jest.fn().mockImplementation(async () => { + throw new DataViewMissingIndices('Catch me if you can!'); + }); + const dataView = await indexPatterns.get(id); + expect(dataView.matchedIndices.length).toBe(0); + }); + test('savedObjectCache pre-fetches title, type, typeMeta', async () => { expect(await indexPatterns.getIds()).toEqual(['id']); expect(savedObjectsClient.find).toHaveBeenCalledWith({ diff --git a/src/plugins/data_views/common/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts index 13fbb9cec243e..afccfdcee79fc 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -569,8 +569,8 @@ export class DataViewsService { }; /** - * Refresh field list for a given index pattern. - * @param indexPattern + * Refresh field list for a given data view. + * @param dataView * @param displayErrors - If set false, API consumer is responsible for displaying and handling errors. */ refreshFields = async (dataView: DataView, displayErrors: boolean = true) => { @@ -582,22 +582,19 @@ export class DataViewsService { await this.refreshFieldsFn(dataView); } catch (err) { if (err instanceof DataViewMissingIndices) { - this.onNotification( - { title: err.message, color: 'danger', iconType: 'error' }, - `refreshFields:${dataView.getIndexPattern()}` + // not considered an error, check dataView.matchedIndices.length to be 0 + } else { + this.onError( + err, + { + title: i18n.translate('dataViews.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for data view {title} (ID: {id})', + values: { id: dataView.id, title: dataView.getIndexPattern() }, + }), + }, + dataView.getIndexPattern() ); } - - this.onError( - err, - { - title: i18n.translate('dataViews.fetchFieldErrorTitle', { - defaultMessage: 'Error fetching fields for data view {title} (ID: {id})', - values: { id: dataView.id, title: dataView.getIndexPattern() }, - }), - }, - dataView.getIndexPattern() - ); } }; @@ -635,14 +632,12 @@ export class DataViewsService { return { fields: this.fieldArrayToMap(updatedFieldList, fieldAttrs), indices }; } catch (err) { if (err instanceof DataViewMissingIndices) { - if (displayErrors) { - this.onNotification( - { title: err.message, color: 'danger', iconType: 'error' }, - `refreshFieldSpecMap:${title}` - ); - } + // not considered an error, check dataView.matchedIndices.length to be 0 return {}; } + if (!displayErrors) { + throw err; + } this.onError( err, @@ -798,19 +793,13 @@ export class DataViewsService { const fieldsAndIndices = await this.initFromSavedObjectLoadFields({ savedObjectId: savedObject.id, spec, + displayErrors, }); fields = fieldsAndIndices.fields; indices = fieldsAndIndices.indices; } catch (err) { if (err instanceof DataViewMissingIndices) { - this.onNotification( - { - title: err.message, - color: 'danger', - iconType: 'error', - }, - `initFromSavedObject:${spec.title}` - ); + // not considered an error, check dataView.matchedIndices.length to be 0 } else { this.onError( err, diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index 5f7b3a544db9d..a26895f6f9d78 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -79,3 +79,4 @@ export type { IndexPatternLoadExpressionFunctionDefinition, } from './expressions'; export { getIndexPatternLoadMeta } from './expressions'; +export { DataViewMissingIndices } from './lib/errors'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index 88a5660db3017..31919d1e2de20 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -243,6 +243,10 @@ describe('discover sidebar field', function () { await comp.update(); expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); + + await new Promise((resolve) => setTimeout(resolve, 0)); + await comp.update(); + expect(findTestSubject(comp, 'dscFieldStats-title').text()).toBe('Top values'); expect(findTestSubject(comp, 'dscFieldStats-topValues-bucket')).toHaveLength(2); expect( diff --git a/src/plugins/embeddable/kibana.jsonc b/src/plugins/embeddable/kibana.jsonc index 89aa3e41026a6..b7a164b459d77 100644 --- a/src/plugins/embeddable/kibana.jsonc +++ b/src/plugins/embeddable/kibana.jsonc @@ -10,7 +10,9 @@ "requiredPlugins": [ "data", "inspector", - "uiActions" + "uiActions", + "savedObjectsFinder", + "savedObjectsManagement" ], "requiredBundles": [ "savedObjects", diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index ff41aa5503e33..18e5484b8fa4d 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -87,6 +87,8 @@ export { EmbeddableRenderer, useEmbeddableFactory, isFilterableEmbeddable, + shouldFetch$, + shouldRefreshFilterCompareOptions, } from './lib'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts b/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts index ffa9470a34af4..0a01b6cab39df 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts @@ -8,3 +8,4 @@ export type { FilterableEmbeddable } from './types'; export { isFilterableEmbeddable } from './types'; +export { shouldFetch$, shouldRefreshFilterCompareOptions } from './should_fetch'; diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx new file mode 100644 index 0000000000000..cf9cba103e011 --- /dev/null +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 fastIsEqual from 'fast-deep-equal'; +import { Observable } from 'rxjs'; +import { map, distinctUntilChanged, skip, startWith } from 'rxjs/operators'; +import { COMPARE_ALL_OPTIONS, onlyDisabledFiltersChanged } from '@kbn/es-query'; +import type { FilterableEmbeddableInput } from './types'; + +export const shouldRefreshFilterCompareOptions = { + ...COMPARE_ALL_OPTIONS, + // do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results) + state: false, +}; + +export function shouldFetch$< + TFilterableEmbeddableInput extends FilterableEmbeddableInput = FilterableEmbeddableInput +>( + updated$: Observable, + getInput: () => TFilterableEmbeddableInput +): Observable { + return updated$.pipe(map(() => getInput())).pipe( + // wrapping distinctUntilChanged with startWith and skip to prime distinctUntilChanged with an initial input value. + startWith(getInput()), + distinctUntilChanged((a: TFilterableEmbeddableInput, b: TFilterableEmbeddableInput) => { + if ( + !fastIsEqual( + [a.searchSessionId, a.query, a.timeRange, a.timeslice], + [b.searchSessionId, b.query, b.timeRange, b.timeslice] + ) + ) { + return false; + } + + return onlyDisabledFiltersChanged(a.filters, b.filters, shouldRefreshFilterCompareOptions); + }), + skip(1) + ); +} diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts b/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts index 8fe2b85e02ada..2b6182b1b95db 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts @@ -6,7 +6,15 @@ * Side Public License, v 1. */ -import { type AggregateQuery, type Filter, type Query } from '@kbn/es-query'; +import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { EmbeddableInput } from '../embeddables'; + +export type FilterableEmbeddableInput = EmbeddableInput & { + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; + timeslice?: [number, number]; +}; /** * All embeddables that implement this interface should support being filtered diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 25ccb141cd356..478845cf66f6d 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -14,6 +14,11 @@ import { type AggregateQuery, type Filter, type Query } from '@kbn/es-query'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; +import { + SavedObjectManagementTypeInfo, + SavedObjectsManagementPluginStart, +} from '@kbn/saved-objects-management-plugin/public'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { UiActionsService } from './lib/ui_actions'; import { EmbeddablePublicPlugin } from './plugin'; import { @@ -156,10 +161,28 @@ const createInstance = (setupPlugins: Partial = {}) const setup = plugin.setup(coreMock.createSetup(), { uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), }); + const savedObjectsManagementMock = { + parseQuery: (query: Query, types: SavedObjectManagementTypeInfo[]) => { + return { + queryText: 'some search', + }; + }, + getTagFindReferences: ({ + selectedTags, + taggingApi, + }: { + selectedTags?: string[]; + taggingApi?: SavedObjectsTaggingApi; + }) => { + return undefined; + }, + }; const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), + savedObjectsManagement: + savedObjectsManagementMock as unknown as SavedObjectsManagementPluginStart, }); return { plugin, diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 1ea92fe129ccf..b064ae3f77592 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -11,7 +11,7 @@ import { Subscription } from 'rxjs'; import { identity } from 'lodash'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; import type { SerializableRecord } from '@kbn/utility-types'; -import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { @@ -23,6 +23,7 @@ import { } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { migrateToLatest, PersistableStateService } from '@kbn/kibana-utils-plugin/common'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider, @@ -64,6 +65,7 @@ export interface EmbeddableSetupDependencies { export interface EmbeddableStartDependencies { uiActions: UiActionsStart; inspector: InspectorStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export interface EmbeddableSetup { @@ -143,7 +145,7 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( @@ -207,7 +209,11 @@ export class EmbeddablePublicPlugin implements Plugin diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index 6587d42dfda12..09412d973ec78 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -11,8 +11,13 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; +import { + SavedObjectManagementTypeInfo, + SavedObjectsManagementPluginStart, +} from '@kbn/saved-objects-management-plugin/public'; +import { Query } from '@kbn/es-query'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; - export interface TestPluginReturn { plugin: EmbeddablePublicPlugin; coreSetup: CoreSetup; @@ -32,6 +37,22 @@ export const testPlugin = ( const setup = plugin.setup(coreSetup, { uiActions: uiActions.setup, }); + const savedObjectsManagementMock = { + parseQuery: (query: Query, types: SavedObjectManagementTypeInfo[]) => { + return { + queryText: 'some search', + }; + }, + getTagFindReferences: ({ + selectedTags, + taggingApi, + }: { + selectedTags?: string[]; + taggingApi?: SavedObjectsTaggingApi; + }) => { + return undefined; + }, + }; return { plugin, @@ -42,6 +63,8 @@ export const testPlugin = ( const start = plugin.start(anotherCoreStart, { inspector: inspectorPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), + savedObjectsManagement: + savedObjectsManagementMock as unknown as SavedObjectsManagementPluginStart, }); return start; }, diff --git a/src/plugins/embeddable/tsconfig.json b/src/plugins/embeddable/tsconfig.json index 67102414915d9..6f8a146187b2a 100644 --- a/src/plugins/embeddable/tsconfig.json +++ b/src/plugins/embeddable/tsconfig.json @@ -30,6 +30,9 @@ "@kbn/data-plugin", "@kbn/core-overlays-browser-mocks", "@kbn/core-theme-browser-mocks", + "@kbn/saved-objects-management-plugin", + "@kbn/saved-objects-tagging-oss-plugin", + "@kbn/saved-objects-finder-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index 8f9a428beb1fd..7dbf443f7f86e 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -2,9 +2,11 @@ This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. -The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. The server-side code is not intended for external use. +The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. -The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins. +The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins that need to react to the guided onboarding state, for example hide or display UI elements if a guide step is active. + +Besides the internal API routes, the server-side code also exposes a function to register guide configs from the server-side setup start contract. This function is intended for external use by any plugin that need to add a new guide or modify an existing one. --- ## Current functionality @@ -106,3 +108,13 @@ The guided onboarding exposes a function `registerGuideConfig(guideId: GuideId, - observability: `x-pack/plugins/observability/server/plugin.ts` - security solution: `x-pack/plugins/security_solution/server/plugin.ts` + +## Adding a new guide +Follow these simple steps to add a new guide to the guided onboarding framework. For more detailed information about framework functionality and architecture and about API services exposed by the plugin, please read the full readme. + +1. Declare the `guidedOnboarding` plugin as a dependency in your plugin's `kibana.json` file. Add the guided onboarding plugin's client-side start contract to your plugin's client-side start dependencies and the guided onboarding plugin's server-side setup contract to your plugin's server-side dependencies. +2. Define the configuration for your guide. At a high level, this includes a title, description, and list of steps. See this [example config](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/common/test_guide_config.ts) or consult the `GuideConfig` interface. +3. Register your guide during your plugin's server-side setup by calling a function exposed by the guided onboarding plugin: `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)`. For an example, see this [example plugin](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/server/plugin.ts). +4. Update the cards on the landing page to include your guide in the use case selection. Make sure that the card doesn't have the property `navigateTo` because that is only used for cards that redirect to Kibana pages and don't start a guide. Also add the same value to the property `guideId` as used in the guide config. Landing page cards are configured in this [kbn-guided-onboarding package](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.tsx). +5. Integrate the new guide into your Kibana pages by using the guided onboarding client-side API service. Make sure your Kibana pages correctly display UI elements depending on the active guide step and the UI flow is straight forward to complete the guide. See existing guides for an example and read more about the API service in this file, the section "Client-side: API service". +6. Optionally, update the example plugin's [form](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/public/components/main.tsx#L38) to be able to start your guide from that page and activate any step in your guide (useful to test your guide steps). diff --git a/src/plugins/saved_objects_finder/kibana.jsonc b/src/plugins/saved_objects_finder/kibana.jsonc index bfe35d688b9cf..9373ef8d5f9de 100644 --- a/src/plugins/saved_objects_finder/kibana.jsonc +++ b/src/plugins/saved_objects_finder/kibana.jsonc @@ -6,8 +6,6 @@ "id": "savedObjectsFinder", "server": true, "browser": true, - "requiredBundles": [ - "savedObjects" - ] + "requiredBundles": ["savedObjects"] } } diff --git a/src/plugins/saved_objects_finder/public/finder/index.tsx b/src/plugins/saved_objects_finder/public/finder/index.tsx index 0819ebf8141b6..789d09ec520b1 100644 --- a/src/plugins/saved_objects_finder/public/finder/index.tsx +++ b/src/plugins/saved_objects_finder/public/finder/index.tsx @@ -8,6 +8,10 @@ import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui'; import React from 'react'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { HttpStart } from '@kbn/core-http-browser'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { SavedObjectFinderProps } from './saved_object_finder'; const LazySavedObjectFinder = React.lazy(() => import('./saved_object_finder')); @@ -23,5 +27,19 @@ const SavedObjectFinder = (props: SavedObjectFinderProps) => ( ); +export const getSavedObjectFinder = ( + uiSettings: IUiSettingsClient, + http: HttpStart, + savedObjectsManagement: SavedObjectsManagementPluginStart, + savedObjectsTagging?: SavedObjectsTaggingApi +) => { + return (props: SavedObjectFinderProps) => ( + + ); +}; + export type { SavedObjectMetaData, SavedObjectFinderProps } from './saved_object_finder'; export { SavedObjectFinder }; diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index 570890e015409..2db81716876c9 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -28,7 +28,7 @@ import { i18n } from '@kbn/i18n'; import type { IUiSettingsClient, HttpStart } from '@kbn/core/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { LISTING_LIMIT_SETTING } from '@kbn/saved-objects-plugin/public'; -import { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP } from '../../common'; +import { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP, FinderAttributes } from '../../common'; export interface SavedObjectMetaData { type: string; @@ -41,12 +41,6 @@ export interface SavedObjectMetaData { defaultSearchField?: string; } -interface FinderAttributes { - title?: string; - name?: string; - type: string; -} - interface SavedObjectFinderItem extends SavedObjectCommon { title: string | null; name: string | null; @@ -64,7 +58,7 @@ interface SavedObjectFinderServices { http: HttpStart; uiSettings: IUiSettingsClient; savedObjectsManagement: SavedObjectsManagementPluginStart; - savedObjectsTagging: SavedObjectsTaggingApi | undefined; + savedObjectsTagging?: SavedObjectsTaggingApi; } interface BaseSavedObjectFinder { @@ -109,16 +103,7 @@ export class SavedObjectFinderUi extends React.Component< private debouncedFetch = debounce(async (query: Query) => { const metaDataMap = this.getSavedObjectMetaDataMap(); - const { queryText, visibleTypes, selectedTags } = - this.props.services.savedObjectsManagement.parseQuery( - query, - Object.values(metaDataMap).map((metadata) => ({ - name: metadata.type, - namespaceType: 'single', - hidden: false, - displayName: metadata.name, - })) - ); + const { savedObjectsManagement, uiSettings, http } = this.props.services; const fields = Object.values(metaDataMap) .map((metaData) => metaData.includeFields || []) @@ -131,22 +116,32 @@ export class SavedObjectFinderUi extends React.Component< return col; }, []); - const perPage = this.props.services.uiSettings.get(LISTING_LIMIT_SETTING); - const hasReference = this.props.services.savedObjectsManagement.getTagFindReferences({ + const perPage = uiSettings.get(LISTING_LIMIT_SETTING); + const { queryText, visibleTypes, selectedTags } = savedObjectsManagement.parseQuery( + query, + Object.values(metaDataMap).map((metadata) => ({ + name: metadata.type, + namespaceType: 'single', + hidden: false, + displayName: metadata.name, + })) + ); + const hasReference = savedObjectsManagement.getTagFindReferences({ selectedTags, taggingApi: this.props.services.savedObjectsTagging, }); const params: FindQueryHTTP = { type: visibleTypes ?? Object.keys(metaDataMap), - fields: [...new Set(fields)], search: queryText ? `${queryText}*` : undefined, + fields: [...new Set(fields)], page: 1, perPage, searchFields: ['title^3', 'description', ...additionalSearchFields], defaultSearchOperator: 'AND', hasReference: hasReference ? JSON.stringify(hasReference) : undefined, }; - const response = (await this.props.services.http.get('/internal/saved-objects-finder/find', { + + const response = (await http.get('/internal/saved-objects-finder/find', { query: params as Record, })) as FindResponseHTTP; @@ -156,7 +151,7 @@ export class SavedObjectFinderUi extends React.Component< attributes: { name, title }, } = savedObject; const titleToUse = typeof title === 'string' ? title : ''; - const nameToUse = name && typeof name === 'string' ? name : titleToUse; + const nameToUse = name ? name : titleToUse; return { ...savedObject, version: savedObject.version, @@ -169,9 +164,8 @@ export class SavedObjectFinderUi extends React.Component< const metaData = metaDataMap[savedObject.type]; if (metaData.showSavedObject) { return metaData.showSavedObject(savedObject.simple); - } else { - return true; } + return true; }); if (!this.isComponentMounted) { @@ -289,7 +283,7 @@ export class SavedObjectFinderUi extends React.Component< name: i18n.translate('savedObjectsFinder.titleName', { defaultMessage: 'Title', }), - width: '55%', + width: tagColumn ? '55%' : '100%', description: i18n.translate('savedObjectsFinder.titleDescription', { defaultMessage: 'Title of the saved object', }), @@ -369,6 +363,7 @@ export class SavedObjectFinderUi extends React.Component< itemId="id" items={this.state.items} columns={columns} + data-test-subj="savedObjectsFinderTable" message={this.props.noItemsMessage} search={search} pagination={pagination} diff --git a/src/plugins/saved_objects_finder/public/index.ts b/src/plugins/saved_objects_finder/public/index.ts index b266860185365..ada561d5ccb0d 100644 --- a/src/plugins/saved_objects_finder/public/index.ts +++ b/src/plugins/saved_objects_finder/public/index.ts @@ -8,6 +8,6 @@ import { SavedObjectsFinderPublicPlugin } from './plugin'; export type { SavedObjectMetaData, SavedObjectFinderProps } from './finder'; -export { SavedObjectFinder } from './finder'; +export { SavedObjectFinder, getSavedObjectFinder } from './finder'; export const plugin = () => new SavedObjectsFinderPublicPlugin(); diff --git a/src/plugins/saved_objects_finder/tsconfig.json b/src/plugins/saved_objects_finder/tsconfig.json index 02aa4bf8208ea..8725d23a5ea14 100644 --- a/src/plugins/saved_objects_finder/tsconfig.json +++ b/src/plugins/saved_objects_finder/tsconfig.json @@ -13,6 +13,8 @@ "@kbn/saved-objects-plugin", "@kbn/core-saved-objects-server", "@kbn/config-schema", + "@kbn/core-ui-settings-browser", + "@kbn/core-http-browser", ], "exclude": [ "target/**/*", diff --git a/src/plugins/saved_objects_management/common/types/v1.ts b/src/plugins/saved_objects_management/common/types/v1.ts index 78bd4a50c036e..241188d035a6e 100644 --- a/src/plugins/saved_objects_management/common/types/v1.ts +++ b/src/plugins/saved_objects_management/common/types/v1.ts @@ -129,13 +129,6 @@ export interface FindQueryHTTP { sortOrder?: FindSortOrderHTTP; hasReference?: ReferenceHTTP | ReferenceHTTP[]; hasReferenceOperator?: FindSearchOperatorHTTP; - /** - * It is not clear who might be using this API option, the SOM UI only ever passes in "id" here. - * - * TODO: Determine use. If not in use we should remove this option. If in use we must deprecate and eventually - * remove. - */ - fields?: string | string[]; } export interface FindResponseHTTP { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 264823dd8e991..48d428a9f087f 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -236,7 +236,6 @@ export class SavedObjectsTable extends Component typeRegistry.isImportableAndExportable(type) @@ -90,9 +86,6 @@ export const registerFindRoute = ( saved_objects: savedObjects.map((so) => { const obj = injectMetaAttributes(so, managementService); const result = { ...obj, attributes: {} as Record }; - for (const field of includedFields) { - result.attributes[field] = (obj.attributes as Record)[field]; - } return result; }), total: findResponse.total, diff --git a/src/plugins/telemetry/common/routes.ts b/src/plugins/telemetry/common/routes.ts index 2161cb7dd5651..06d6f746bf2c1 100644 --- a/src/plugins/telemetry/common/routes.ts +++ b/src/plugins/telemetry/common/routes.ts @@ -10,9 +10,3 @@ * Fetch Telemetry Config */ export const FetchTelemetryConfigRoute = '/api/telemetry/v2/config'; -export interface FetchTelemetryConfigResponse { - allowChangingOptInStatus: boolean; - optIn: boolean | null; - sendUsageFrom: 'server' | 'browser'; - telemetryNotifyUserAboutOptInDefault: boolean; -} diff --git a/src/plugins/telemetry/common/types.ts b/src/plugins/telemetry/common/types/index.ts similarity index 66% rename from src/plugins/telemetry/common/types.ts rename to src/plugins/telemetry/common/types/index.ts index aefbbd2358861..14b2d3cbefcf4 100644 --- a/src/plugins/telemetry/common/types.ts +++ b/src/plugins/telemetry/common/types/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export type EncryptedTelemetryPayload = Array<{ clusterUuid: string; stats: string }>; -export type UnencryptedTelemetryPayload = Array<{ clusterUuid: string; stats: object }>; +export * from './latest'; + +export * as v2 from './v2'; diff --git a/src/plugins/telemetry/common/types/latest.ts b/src/plugins/telemetry/common/types/latest.ts new file mode 100644 index 0000000000000..557f34eac9ee2 --- /dev/null +++ b/src/plugins/telemetry/common/types/latest.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './v2'; diff --git a/src/plugins/telemetry/common/types/v2.ts b/src/plugins/telemetry/common/types/v2.ts new file mode 100644 index 0000000000000..dc90ad3d242a6 --- /dev/null +++ b/src/plugins/telemetry/common/types/v2.ts @@ -0,0 +1,71 @@ +/* + * 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 interface Telemetry { + /** Whether telemetry is enabled */ + enabled?: boolean | null; + lastVersionChecked?: string; + /** Whether to send usage from the server or browser. */ + sendUsageFrom?: 'browser' | 'server'; + lastReported?: number; + allowChangingOptInStatus?: boolean; + userHasSeenNotice?: boolean; + reportFailureCount?: number; + reportFailureVersion?: string; +} + +export interface FetchTelemetryConfigResponse { + allowChangingOptInStatus: boolean; + optIn: boolean | null; + sendUsageFrom: 'server' | 'browser'; + telemetryNotifyUserAboutOptInDefault: boolean; +} + +export interface FetchLastReportedResponse { + lastReported: undefined | number; +} + +export type UpdateLastReportedResponse = undefined; + +export interface OptInStatsBody { + enabled: boolean; + /** @default true */ + unencrypted?: boolean; +} + +export interface StatsPayload { + cluster_uuid: string; + opt_in_status: boolean; +} + +export type OptInStatsResponse = Array<{ + clusterUuid: string; + stats: StatsPayload; +}>; + +export interface OptInBody { + enabled: boolean; +} + +export type OptInResponse = Array<{ + clusterUuid: string; + stats: string; +}>; + +export interface UsageStatsBody { + /** @default false */ + unencrypted: boolean; + /** @default false */ + refreshCache: boolean; +} + +export type UseHasSeenNoticeResponse = Telemetry; + +export type EncryptedTelemetryPayload = Array<{ clusterUuid: string; stats: string }>; + +export type UnencryptedTelemetryPayload = Array<{ clusterUuid: string; stats: object }>; diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index d28868a6dd286..15b581c498366 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -21,7 +21,8 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser'; import { of } from 'rxjs'; -import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../common/routes'; +import { FetchTelemetryConfigRoute } from '../common/routes'; +import type { v2 } from '../common/types'; import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services'; import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice'; @@ -322,7 +323,7 @@ export class TelemetryPlugin implements Plugin { const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } = - await http.get(FetchTelemetryConfigRoute); + await http.get(FetchTelemetryConfigRoute); return { ...this.config, diff --git a/src/plugins/telemetry/public/services/telemetry_sender.ts b/src/plugins/telemetry/public/services/telemetry_sender.ts index 6ffed583ec95c..3006a328fb620 100644 --- a/src/plugins/telemetry/public/services/telemetry_sender.ts +++ b/src/plugins/telemetry/public/services/telemetry_sender.ts @@ -12,7 +12,7 @@ import { exhaustMap } from 'rxjs/operators'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { LOCALSTORAGE_KEY, PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; import { TelemetryService } from './telemetry_service'; -import type { EncryptedTelemetryPayload } from '../../common/types'; +import type { EncryptedTelemetryPayload } from '../../common/types/latest'; import { isReportIntervalExpired } from '../../common/is_report_interval_expired'; export class TelemetrySender { diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 189df46b2328b..c1b96f4749067 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -10,7 +10,10 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from '@kbn/core/public'; import { TelemetryPluginConfig } from '../plugin'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint'; -import type { UnencryptedTelemetryPayload, EncryptedTelemetryPayload } from '../../common/types'; +import type { + UnencryptedTelemetryPayload, + EncryptedTelemetryPayload, +} from '../../common/types/latest'; import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; interface TelemetryServiceConstructor { diff --git a/src/plugins/telemetry/server/routes/telemetry_config.ts b/src/plugins/telemetry/server/routes/telemetry_config.ts index 4bcf1d1f4c811..60a34d80aad2e 100644 --- a/src/plugins/telemetry/server/routes/telemetry_config.ts +++ b/src/plugins/telemetry/server/routes/telemetry_config.ts @@ -9,7 +9,8 @@ import { type Observable, firstValueFrom } from 'rxjs'; import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; import type { TelemetryConfigType } from '../config'; -import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../../common/routes'; +import { v2 } from '../../common/types'; +import { FetchTelemetryConfigRoute } from '../../common/routes'; import { getTelemetrySavedObject } from '../saved_objects'; import { getNotifyUserAboutOptInDefault, @@ -64,7 +65,7 @@ export function registerTelemetryConfigRoutes({ telemetryOptedIn: optIn, }); - const body: FetchTelemetryConfigResponse = { + const body: v2.FetchTelemetryConfigResponse = { allowChangingOptInStatus, optIn, sendUsageFrom, diff --git a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts index 80037761895b2..2e21785b9296d 100644 --- a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts +++ b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts @@ -9,6 +9,7 @@ import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; +import { v2 } from '../../common/types'; import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../saved_objects'; export function registerTelemetryLastReported( @@ -25,10 +26,12 @@ export function registerTelemetryLastReported( const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); + const body: v2.FetchLastReportedResponse = { + lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, + }; + return res.ok({ - body: { - lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, - }, + body, }); } ); diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index e26e8c596b53a..cc477c4f23198 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -14,6 +14,7 @@ import type { StatsGetterConfig, TelemetryCollectionManagerPluginSetup, } from '@kbn/telemetry-collection-manager-plugin/server'; +import { v2 } from '../../common/types'; import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats'; import { getTelemetrySavedObject, @@ -109,7 +110,9 @@ export function registerTelemetryOptInRoutes({ return res.forbidden(); } } - return res.ok({ body: optInStatus }); + + const body: v2.OptInResponse = optInStatus; + return res.ok({ body }); } ); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index d8ec9d4922fc1..8c9c85172ced6 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -14,6 +14,7 @@ import type { TelemetryCollectionManagerPluginSetup, StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; +import type { v2 } from '../../common/types'; import { EncryptedTelemetryPayload, UnencryptedTelemetryPayload } from '../../common/types'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; @@ -90,7 +91,8 @@ export function registerTelemetryOptInStatsRoutes( newOptInStatus, statsGetterConfig ); - return res.ok({ body: optInStatus }); + const body: v2.OptInStatsResponse = optInStatus; + return res.ok({ body }); } catch (err) { return res.ok({ body: [] }); } diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index fd613e1318966..53169367b965e 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -13,6 +13,7 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { v2 } from '../../common/types'; export type SecurityGetter = () => SecurityPluginStart | undefined; @@ -64,8 +65,10 @@ export function registerTelemetryUsageStatsRoutes( refreshCache: unencrypted || refreshCache, }; - const stats = await telemetryCollectionManager.getStats(statsConfig); - return res.ok({ body: stats }); + const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats( + statsConfig + ); + return res.ok({ body }); } catch (err) { if (isDev) { // don't ignore errors when running in dev mode diff --git a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts index 7686aa4100755..eeac24c0f5a07 100644 --- a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts +++ b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts @@ -8,6 +8,7 @@ import type { IRouter } from '@kbn/core/server'; import { TELEMETRY_SAVED_OBJECT_TYPE } from '../saved_objects'; +import { v2 } from '../../common/types'; import { type TelemetrySavedObjectAttributes, getTelemetrySavedObject, @@ -33,7 +34,17 @@ export function registerTelemetryUserHasSeenNotice(router: IRouter) { }; await updateTelemetrySavedObject(soClient, updatedAttributes); - return res.ok({ body: updatedAttributes }); + const body: v2.Telemetry = { + allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus, + enabled: updatedAttributes.enabled, + lastReported: updatedAttributes.lastReported, + lastVersionChecked: updatedAttributes.lastVersionChecked, + reportFailureCount: updatedAttributes.reportFailureCount, + reportFailureVersion: updatedAttributes.reportFailureVersion, + sendUsageFrom: updatedAttributes.sendUsageFrom, + userHasSeenNotice: updatedAttributes.userHasSeenNotice, + }; + return res.ok({ body }); } ); } diff --git a/src/plugins/unified_search/kibana.jsonc b/src/plugins/unified_search/kibana.jsonc index 008b9d9fe03d2..5f146723b7c2e 100644 --- a/src/plugins/unified_search/kibana.jsonc +++ b/src/plugins/unified_search/kibana.jsonc @@ -17,7 +17,8 @@ "dataViews", "data", "uiActions", - "screenshotMode" + "screenshotMode", + "savedObjectsManagement" ], "optionalPlugins": [ "usageCollection" diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 557c31865a417..66abc195b5b19 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -16,6 +16,7 @@ import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collectio import { Query, AggregateQuery } from '@kbn/es-query'; import { CoreStart, DocLinksStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; @@ -92,4 +93,5 @@ export interface IUnifiedSearchPluginServices extends Partial { dataViews: DataViewsPublicPluginStart; dataViewEditor: DataViewEditorStart; usageCollection?: UsageCollectionStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } diff --git a/src/plugins/unified_search/tsconfig.json b/src/plugins/unified_search/tsconfig.json index 0a4ab525d04b7..d11390cf0d18a 100644 --- a/src/plugins/unified_search/tsconfig.json +++ b/src/plugins/unified_search/tsconfig.json @@ -38,6 +38,7 @@ "@kbn/utility-types-jest", "@kbn/react-field", "@kbn/ui-theme", + "@kbn/saved-objects-management-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js index 0aaf011053718..34d933c2e9ae4 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js @@ -62,7 +62,7 @@ export function ratios(req, panel, series, esQueryConfig, seriesIndex) { denominator: denominatorPath, }, script: - 'params.numerator != null && params.denominator != null && params.denominator > 0 ? params.numerator / params.denominator : 0', + 'params.numerator != null && params.denominator != null && params.denominator != 0 ? params.numerator / params.denominator : 0', }, }); }); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index a93827ba82cd6..e11414d88fd5c 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -73,7 +73,7 @@ describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { }, script: 'params.numerator != null && params.denominator != null &&' + - ' params.denominator > 0 ? params.numerator / params.denominator : 0', + ' params.denominator != 0 ? params.numerator / params.denominator : 0', }, }, 'metric-1-denominator': { @@ -150,7 +150,7 @@ describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { }, script: 'params.numerator != null && params.denominator != null &&' + - ' params.denominator > 0 ? params.numerator / params.denominator : 0', + ' params.denominator != 0 ? params.numerator / params.denominator : 0', }, }, 'metric-1-denominator': { diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts index a897232b27554..3f69c7ec0b99b 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts @@ -63,7 +63,7 @@ export const filterRatios: TableRequestProcessorsFunction = ({ denominator: denominatorPath, }, script: - 'params.numerator != null && params.denominator != null && params.denominator > 0 ? params.numerator / params.denominator : 0', + 'params.numerator != null && params.denominator != null && params.denominator != 0 ? params.numerator / params.denominator : 0', }, }); }); diff --git a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap index f49b0bb75951f..a81418c79bb0b 100644 --- a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap +++ b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
    "`; +exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
    "`; exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
    • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
    "`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
    • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
    "`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
    • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
    "`; diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index ca891986f609a..ca78702925d5c 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -37,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return 200 with individual responses', async () => await supertest - .get('/api/kibana/management/saved_objects/_find?type=visualization&fields=title') + .get('/api/kibana/management/saved_objects/_find?type=visualization') .expect(200) .then((resp: Response) => { expect(resp.body.saved_objects.map((so: { id: string }) => so.id)).to.eql([ diff --git a/test/examples/embeddables/adding_children.ts b/test/examples/embeddables/adding_children.ts index 1b99413b184f7..7b3e48151fd17 100644 --- a/test/examples/embeddables/adding_children.ts +++ b/test/examples/embeddables/adding_children.ts @@ -12,8 +12,29 @@ import { PluginFunctionalProviderContext } from '../../plugin_functional/service // eslint-disable-next-line import/no-default-export export default function ({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); const flyout = getService('flyout'); + const toggleFilterPopover = async () => { + const filtersHolder = await find.byClassName('euiSearchBar__filtersHolder'); + const filtersButton = await filtersHolder.findByCssSelector('button'); + await filtersButton.click(); + }; + + const clickFilter = async (type: string) => { + const list = await testSubjects.find('euiSelectableList'); + const listItems = await list.findAllByCssSelector('li'); + for (let i = 0; i < listItems.length; i++) { + const listItem = await listItems[i].findByClassName('euiSelectableListItem__text'); + const text = await listItem.getVisibleText(); + if (text.includes(type)) { + await listItem.click(); + await toggleFilterPopover(); + break; + } + } + }; + describe('adding children', () => { before(async () => { await testSubjects.click('embeddablePanelExample'); @@ -23,8 +44,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) { await testSubjects.click('embeddablePanelToggleMenuIcon'); await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); await testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); - await testSubjects.click('savedObjectFinderFilterButton'); - await testSubjects.click('savedObjectFinderFilter-todo'); + await toggleFilterPopover(); + await clickFilter('Todo'); await testSubjects.click('savedObjectTitleGarbage'); await testSubjects.moveMouseTo('euiFlyoutCloseButton'); await flyout.ensureClosed('dashboardAddPanel'); diff --git a/test/functional/apps/console/_comments.ts b/test/functional/apps/console/_comments.ts index 3250035275976..de8bcda60786b 100644 --- a/test/functional/apps/console/_comments.ts +++ b/test/functional/apps/console/_comments.ts @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe('console app', function testComments() { + // Failing: See https://github.com/elastic/kibana/issues/138160 + describe.skip('console app', function testComments() { this.tags('includeFirefox'); before(async () => { log.debug('navigateTo console'); diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index d7a591efca014..091ad8ee5a2e8 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -36,14 +36,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('navigateTo console'); await PageObjects.common.navigateToApp('console'); }); + beforeEach(async () => { + await PageObjects.console.closeHelpIfExists(); + }); it('should show the default request', async () => { - // collapse the help pane because we only get the VISIBLE TEXT, not the part that is scrolled - // on IE11, the dialog that says 'Your browser does not meet the security requirements for Kibana.' - // blocks the close help button for several seconds so just retry until we can click it. - await retry.try(async () => { - await PageObjects.console.collapseHelp(); - }); await retry.try(async () => { const actualRequest = await PageObjects.console.getRequest(); log.debug(actualRequest); @@ -182,6 +179,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); expect(await PageObjects.console.hasFolds()).to.be(false); }); + + it(`doesn't fail if a fold fails`, async () => { + // for more details, see https://github.com/elastic/kibana/issues/151563 + await browser.clearLocalStorage(); + await browser.setLocalStorageItem( + 'sense:folds', + '[{"start":{"row":1,"column":1},"end":{"row":82,"column":4}}]' + ); + await browser.setLocalStorageItem( + 'sense:console_local_text-object_95a511b6-b6e1-4ea6-9344-428bf5183d88', + '{"id":"95a511b6-b6e1-4ea6-9344-428bf5183d88","createdAt":1677592109975,"updatedAt":1677592148666,"text":"GET _cat/indices"}' + ); + await browser.refresh(); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.console.closeHelpIfExists(); + const request = await PageObjects.console.getRequest(); + // the request is restored from the local storage value + expect(request).to.eql('GET _cat/indices'); + await browser.clearLocalStorage(); + }); }); }); } diff --git a/test/functional/apps/context/_discover_navigation.ts b/test/functional/apps/context/_discover_navigation.ts index 9f8e86ad7352e..24da7da392190 100644 --- a/test/functional/apps/context/_discover_navigation.ts +++ b/test/functional/apps/context/_discover_navigation.ts @@ -48,6 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { await filterBar.addFilter({ field: columnName, operation: 'is', value }); + await PageObjects.header.waitUntilLoadingHasFinished(); } }); @@ -82,7 +83,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('next anchor timestamp matches previous anchor timestamp', async () => { // get the timestamp of the first row const firstContextTimestamp = await getTimestamp(false); - await dataGrid.clickRowToggle({ isAnchorRow: true }); + await dataGrid.clickRowToggle({ rowIndex: 0 }); const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); await rowActions[1].click(); diff --git a/test/functional/apps/dashboard/group2/embeddable_library.ts b/test/functional/apps/dashboard/group2/embeddable_library.ts index ca52eaecaf46e..472a2a890c978 100644 --- a/test/functional/apps/dashboard/group2/embeddable_library.ts +++ b/test/functional/apps/dashboard/group2/embeddable_library.ts @@ -17,6 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); const panelActions = getService('dashboardPanelActions'); + const savedObjectsFinder = getService('savedObjectsFinder'); describe('embeddable library', () => { before(async () => { @@ -35,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('unlink visualize panel from embeddable library', async () => { // add heatmap panel from library await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: heatmap'); + await savedObjectsFinder.filterEmbeddableNames('Rendering Test: heatmap'); await find.clickByButtonText('Rendering Test: heatmap'); await dashboardAddPanel.closeAddPanel(); @@ -51,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(libraryActionExists).to.be(false); await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: heatmap'); + await savedObjectsFinder.filterEmbeddableNames('Rendering Test: heatmap'); await find.existsByLinkText('Rendering Test: heatmap'); await dashboardAddPanel.closeAddPanel(); }); diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index c280de155be82..7839315cb2239 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -8,13 +8,15 @@ import expect from '@kbn/expect'; import chroma from 'chroma-js'; - +import rison from '@kbn/rison'; import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/public/dashboard_constants'; +import type { SharedDashboardState } from '@kbn/dashboard-plugin/common'; import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../../page_objects/dashboard_page'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ + 'common', 'dashboard', 'visualize', 'header', @@ -31,6 +33,36 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const dashboardAddPanel = getService('dashboardAddPanel'); const xyChartSelector = 'xyVisChart'; + const log = getService('log'); + + const updateAppStateQueryParam = ( + url: string, + setAppState: (appState: Partial) => Partial + ) => { + log.debug(`updateAppStateQueryParam, before url: ${url}`); + + // Using lastIndexOf because URL may have 2 sets of query parameters. + // 1) server query parameters, '_t' + // 2) client query parameters, '_g' and '_a'. Anything after the '#' in a URL is used by the client + // Example shape of URL http://localhost:5620/app/dashboards?_t=12345#/create?_g=() + const clientQueryParamsStartIndex = url.lastIndexOf('?'); + if (clientQueryParamsStartIndex === -1) { + throw Error(`Unable to locate query parameters in URL: ${url}`); + } + const urlBeforeClientQueryParams = url.substring(0, clientQueryParamsStartIndex); + const urlParams = new URLSearchParams(url.substring(clientQueryParamsStartIndex + 1)); + const appState: Partial = urlParams.has('_a') + ? (rison.decode(urlParams.get('_a')!) as Partial) + : {}; + const newAppState = { + ...appState, + ...setAppState(appState), + }; + urlParams.set('_a', rison.encode(newAppState)); + const newUrl = urlBeforeClientQueryParams + '?' + urlParams.toString(); + log.debug(`updateAppStateQueryParam, after url: ${newUrl}`); + return newUrl; + }; const enableNewChartLibraryDebug = async (force = false) => { if ((await PageObjects.visChart.isNewChartsLibraryEnabled()) || force) { @@ -39,10 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }; - // Failing: See https://github.com/elastic/kibana/issues/139762 - describe.skip('dashboard state', function describeIndexTests() { - // Used to track flag before and after reset - + describe('dashboard state', function () { before(async function () { await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); @@ -140,8 +169,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Saved search will update when the query is changed in the URL', async () => { const currentQuery = await queryBar.getQueryString(); expect(currentQuery).to.equal(''); - const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(`query:''`, `query:'abc12345678910'`); + const newUrl = updateAppStateQueryParam( + await getUrlFromShare(), + (appState: Partial) => { + return { + query: { + language: 'kuery', + query: 'abc12345678910', + }, + }; + } + ); // We need to add a timestamp to the URL because URL changes now only work with a hard refresh. await browser.get(newUrl.toString()); @@ -153,9 +191,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); const getUrlFromShare = async () => { + log.debug(`getUrlFromShare`); await PageObjects.share.clickShareTopNavButton(); const sharedUrl = await PageObjects.share.getSharedUrl(); await PageObjects.share.clickShareTopNavButton(); + log.debug(`sharedUrl: ${sharedUrl}`); return sharedUrl; }; @@ -177,11 +217,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const changeQuery = async (useHardRefresh: boolean, newQuery: string) => { await queryBar.clickQuerySubmitButton(); - const oldQuery = await queryBar.getQueryString(); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(`query:'${oldQuery}'`, `query:'${newQuery}'`); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + query: { + language: 'kuery', + query: newQuery, + }, + }; + } + ); await browser.get(newUrl.toString(), !useHardRefresh); + await PageObjects.dashboard.waitForRenderComplete(); const queryBarContentsAfterRefresh = await queryBar.getQueryString(); expect(queryBarContentsAfterRefresh).to.equal(newQuery); }; @@ -202,9 +252,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); const currentUrl = await getUrlFromShare(); const currentPanelDimensions = await PageObjects.dashboard.getPanelDimensions(); - const newUrl = currentUrl.replace( - `w:${DEFAULT_PANEL_WIDTH}`, - `w:${DEFAULT_PANEL_WIDTH * 2}` + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + log.debug(JSON.stringify(appState, null, ' ')); + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + gridData: { + ...panel.gridData, + w: + panel.gridData.w === DEFAULT_PANEL_WIDTH + ? DEFAULT_PANEL_WIDTH * 2 + : panel.gridData.w, + }, + }; + }), + }; + } ); await hardRefresh(newUrl); @@ -229,7 +295,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('when removing a panel', async function () { await PageObjects.dashboard.waitForRenderComplete(); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(/panels:\!\(.*\),query/, 'panels:!(),query'); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: [], + }; + } + ); await hardRefresh(newUrl); await retry.try(async () => { @@ -255,7 +328,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.visChart.selectNewLegendColorChoice('#F9D9F9'); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace('F9D9F9', 'FFFFFF'); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + embeddableConfig: { + ...(panel.embeddableConfig ?? {}), + vis: { + ...((panel.embeddableConfig?.vis as object) ?? {}), + colors: { + ...((panel.embeddableConfig?.vis as { colors: object })?.colors ?? {}), + ['80000']: 'FFFFFF', + }, + }, + }, + }; + }), + }; + } + ); await hardRefresh(newUrl); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -280,7 +374,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('resets a pie slice color to the original when removed', async function () { const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(`'80000':%23FFFFFF`, ''); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + embeddableConfig: { + ...(panel.embeddableConfig ?? {}), + vis: { + ...((panel.embeddableConfig?.vis as object) ?? {}), + colors: {}, + }, + }, + }; + }), + }; + } + ); await hardRefresh(newUrl); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/group3/panel_replacing.ts b/test/functional/apps/dashboard/group3/panel_replacing.ts index e6ff8c4f940bb..1cb344748594f 100644 --- a/test/functional/apps/dashboard/group3/panel_replacing.ts +++ b/test/functional/apps/dashboard/group3/panel_replacing.ts @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.switchToEditMode(); } await dashboardPanelActions.replacePanelByTitle(AREA_CHART_VIS_NAME); - await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'search'); + await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'Saved search'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); const panelTitles = await PageObjects.dashboard.getPanelTitles(); diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index e451eee881335..03608566969a5 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -270,5 +270,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(canvasExists).to.be(false); }); }); + + it('should recover from broken query search when clearing the query bar', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + // Make sure the chart is visible + await testSubjects.click('unifiedHistogramChartOptionsToggle'); + await testSubjects.click('unifiedHistogramChartToggle'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + // type an invalid search query, hit refresh + await queryBar.setQuery('this is > not valid'); + await queryBar.submitQuery(); + // check the error state + expect(await testSubjects.exists('embeddable-lens-failure')).to.be(true); + + // now remove the query + await queryBar.clearQuery(); + await queryBar.submitQuery(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + // check no error state + expect(await PageObjects.discover.isChartVisible()).to.be(true); + }); }); } diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index a29fd8046e7ec..7af97ef7ff32f 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { FtrService } from '../../ftr_provider_context'; export class DashboardAddPanelService extends FtrService { @@ -15,6 +14,7 @@ export class DashboardAddPanelService extends FtrService { private readonly flyout = this.ctx.getService('flyout'); private readonly common = this.ctx.getPageObject('common'); private readonly header = this.ctx.getPageObject('header'); + private readonly savedObjectsFinder = this.ctx.getService('savedObjectsFinder'); async clickOpenAddPanel() { this.log.debug('DashboardAddPanel.clickOpenAddPanel'); @@ -77,34 +77,20 @@ export class DashboardAddPanelService extends FtrService { await this.testSubjects.click(`createNew-${type}`); } - async toggleFilterPopover() { - this.log.debug('DashboardAddPanel.toggleFilter'); - await this.testSubjects.click('savedObjectFinderFilterButton'); - } - - async toggleFilter(type: string) { - this.log.debug(`DashboardAddPanel.addToFilter(${type})`); - await this.waitForListLoading(); - await this.toggleFilterPopover(); - await this.testSubjects.click(`savedObjectFinderFilter-${type}`); - await this.toggleFilterPopover(); - } - async addEveryEmbeddableOnCurrentPage() { this.log.debug('addEveryEmbeddableOnCurrentPage'); - const itemList = await this.testSubjects.find('savedObjectFinderItemList'); + const itemList = await this.testSubjects.find('savedObjectsFinderTable'); const embeddableList: string[] = []; await this.retry.try(async () => { - const embeddableRows = await itemList.findAllByCssSelector('li'); + const embeddableListBody = await itemList.findByTagName('tbody'); + const embeddableRows = await embeddableListBody.findAllByCssSelector('tr'); for (let i = 0; i < embeddableRows.length; i++) { - const name = await embeddableRows[i].getVisibleText(); - + const { name, button } = await this.savedObjectsFinder.getRowAtIndex(embeddableRows, i); if (embeddableList.includes(name)) { // already added this one continue; } - - await embeddableRows[i].click(); + await button.click(); await this.common.closeToast(); embeddableList.push(name); } @@ -159,21 +145,21 @@ export class DashboardAddPanelService extends FtrService { } } - async waitForListLoading() { - await this.testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); - } - async closeAddPanel() { await this.flyout.ensureClosed('dashboardAddPanel'); } + async filterEmbeddableNames(name: string) { + await this.savedObjectsFinder.filterEmbeddableNames(name); + } + async addEveryVisualization(filter: string) { this.log.debug('DashboardAddPanel.addEveryVisualization'); await this.ensureAddPanelIsShowing(); - await this.toggleFilter('visualization'); if (filter) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } + await this.savedObjectsFinder.waitForFilter('Visualization', 'search'); let morePages = true; const vizList: string[][] = []; while (morePages) { @@ -187,11 +173,11 @@ export class DashboardAddPanelService extends FtrService { async addEverySavedSearch(filter: string) { this.log.debug('DashboardAddPanel.addEverySavedSearch'); await this.ensureAddPanelIsShowing(); - await this.toggleFilter('search'); const searchList = []; if (filter) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } + await this.savedObjectsFinder.waitForFilter('Saved search', 'visualization'); let morePages = true; while (morePages) { searchList.push(await this.addEveryEmbeddableOnCurrentPage()); @@ -222,7 +208,8 @@ export class DashboardAddPanelService extends FtrService { } async addVisualization(vizName: string) { - return this.addEmbeddable(vizName, 'visualization'); + this.log.debug(`DashboardAddPanel.addVisualization, ${vizName}`); + return this.addEmbeddable(vizName, 'Visualization'); } async addEmbeddable(embeddableName: string, embeddableType: string) { @@ -230,25 +217,18 @@ export class DashboardAddPanelService extends FtrService { `DashboardAddPanel.addEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); await this.ensureAddPanelIsShowing(); - await this.toggleFilter(embeddableType); - await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); + await this.savedObjectsFinder.toggleFilter(embeddableType); + await this.savedObjectsFinder.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); await this.testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); await this.testSubjects.exists('addObjectToDashboardSuccess'); await this.closeAddPanel(); return embeddableName; } - async filterEmbeddableNames(name: string) { - // The search input field may be disabled while the table is loading so wait for it - await this.waitForListLoading(); - await this.testSubjects.setValue('savedObjectFinderSearchInput', name); - await this.waitForListLoading(); - } - async panelAddLinkExists(name: string) { this.log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); - await this.filterEmbeddableNames(`"${name}"`); + await this.savedObjectsFinder.filterEmbeddableNames(`"${name}"`); return await this.testSubjects.exists(`savedObjectTitle${name.split(' ').join('-')}`); } } diff --git a/test/functional/services/dashboard/replace_panel.ts b/test/functional/services/dashboard/replace_panel.ts index 8f8f680b839bf..915e65467a7a5 100644 --- a/test/functional/services/dashboard/replace_panel.ts +++ b/test/functional/services/dashboard/replace_panel.ts @@ -12,19 +12,7 @@ export class DashboardReplacePanelService extends FtrService { private readonly log = this.ctx.getService('log'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly flyout = this.ctx.getService('flyout'); - - async toggleFilterPopover() { - this.log.debug('DashboardReplacePanel.toggleFilter'); - await this.testSubjects.click('savedObjectFinderFilterButton'); - } - - async toggleFilter(type: string) { - this.log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); - await this.waitForListLoading(); - await this.toggleFilterPopover(); - await this.testSubjects.click(`savedObjectFinderFilter-${type}`); - await this.toggleFilterPopover(); - } + private readonly savedObjectsFinder = this.ctx.getService('savedObjectsFinder'); async isReplacePanelOpen() { this.log.debug('DashboardReplacePanel.isReplacePanelOpen'); @@ -66,10 +54,10 @@ export class DashboardReplacePanelService extends FtrService { `DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); await this.ensureReplacePanelIsShowing(); + await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); if (embeddableType) { - await this.toggleFilter(embeddableType); + await this.savedObjectsFinder.toggleFilter(embeddableType); } - await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); await this.testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); await this.testSubjects.exists('addObjectToDashboardSuccess'); await this.closeReplacePanel(); diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index 7e28176e764e7..656efa1b002f9 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -304,6 +304,7 @@ export class FilterBarService extends FtrService { await this.testSubjects.clickWhenNotDisabled('saveFilter'); }); + await this.testSubjects.waitForDeleted('saveFilter'); await this.header.awaitGlobalLoadingIndicatorHidden(); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 80b80ada9a979..e13cac581ce52 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -55,6 +55,7 @@ import { KibanaSupertestProvider } from './supertest'; import { MenuToggleService } from './menu_toggle'; import { MonacoEditorService } from './monaco_editor'; import { UsageCollectionService } from './usage_collection'; +import { SavedObjectsFinderService } from './saved_objects_finder'; export const services = { ...commonServiceProviders, @@ -100,4 +101,5 @@ export const services = { menuToggle: MenuToggleService, retryOnStale: RetryOnStaleProvider, usageCollection: UsageCollectionService, + savedObjectsFinder: SavedObjectsFinderService, }; diff --git a/test/functional/services/saved_objects_finder.ts b/test/functional/services/saved_objects_finder.ts new file mode 100644 index 0000000000000..12e06fe8a1710 --- /dev/null +++ b/test/functional/services/saved_objects_finder.ts @@ -0,0 +1,71 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrService } from '../ftr_provider_context'; +import { WebElementWrapper } from './lib/web_element_wrapper'; + +export class SavedObjectsFinderService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + + public async toggleFilterPopover() { + this.log.debug('SavedObjectsFinder.toggleFilter'); + const filtersHolder = await this.find.byClassName('euiSearchBar__filtersHolder'); + const filtersButton = await filtersHolder.findByCssSelector('button'); + await filtersButton.click(); + } + + public async toggleFilter(type: string) { + this.log.debug(`SavedObjectsFinder.addToFilter(${type})`); + await this.waitForListLoading(); + await this.toggleFilterPopover(); + const list = await this.testSubjects.find('euiSelectableList'); + const listItems = await list.findAllByCssSelector('li'); + for (let i = 0; i < listItems.length; i++) { + const listItem = await listItems[i].findByClassName('euiSelectableListItem__text'); + const text = await listItem.getVisibleText(); + if (text.includes(type)) { + await listItem.click(); + await this.toggleFilterPopover(); + break; + } + } + } + + public async waitForFilter(type: string, expectCondition: string) { + await this.toggleFilter(type); + const itemList = await this.testSubjects.find('savedObjectsFinderTable'); + await this.retry.try(async () => { + const embeddableListBody = await itemList.findByTagName('tbody'); + const embeddableRows = await embeddableListBody.findAllByCssSelector('tr'); + const { name } = await this.getRowAtIndex(embeddableRows, 0); + expect(name.includes(expectCondition)).to.be(false); + }); + } + + public async filterEmbeddableNames(name: string) { + // The search input field may be disabled while the table is loading so wait for it + await this.waitForListLoading(); + await this.testSubjects.setValue('savedObjectFinderSearchInput', name); + await this.waitForListLoading(); + } + + public async getRowAtIndex(rows: WebElementWrapper[], rowIndex: number) { + const cell = await rows[rowIndex].findByTestSubject('savedObjectFinderTitle'); + const button = await cell.findByTagName('button'); + const name = await button.getVisibleText(); + return { button, name }; + } + + private async waitForListLoading() { + await this.testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); + } +} diff --git a/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts b/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts index 5a31da2b57a8e..c73edfc106668 100644 --- a/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts +++ b/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts @@ -27,7 +27,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { it('returns empty response for importableAndExportable types', async () => await supertest - .get('/api/saved_objects/_find?type=test-hidden-importable-exportable&fields=title') + .get('/api/saved_objects/_find?type=test-hidden-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { @@ -41,7 +41,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { it('returns empty response for non importableAndExportable types', async () => await supertest - .get('/api/saved_objects/_find?type=test-hidden-non-importable-exportable&fields=title') + .get('/api/saved_objects/_find?type=test-hidden-non-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/test/plugin_functional/test_suites/saved_objects_management/find.ts b/test/plugin_functional/test_suites/saved_objects_management/find.ts index fdeb2c6f8b124..6492f7439079b 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/find.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/find.ts @@ -27,9 +27,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { ); it('returns saved objects with importableAndExportable types', async () => await supertest - .get( - '/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable&fields=title' - ) + .get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts index ce441d9d1b353..d9516ee0331c4 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts @@ -107,9 +107,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { describe('find', () => { it('returns saved objects registered as hidden from the http Apis', async () => { await supertest - .get( - `/api/kibana/management/saved_objects/_find?type=${hiddenFromHttpApisType.type}&fields=title` - ) + .get(`/api/kibana/management/saved_objects/_find?type=${hiddenFromHttpApisType.type}`) .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/x-pack/packages/ml/date_picker/index.ts b/x-pack/packages/ml/date_picker/index.ts index f795d6a4d1f06..1a949a5d1e1d1 100644 --- a/x-pack/packages/ml/date_picker/index.ts +++ b/x-pack/packages/ml/date_picker/index.ts @@ -23,6 +23,7 @@ export { export { getTimeFilterRange, type TimeRange, + type SetFullTimeRangeApiPath, } from './src/services/full_time_range_selector_service'; export { type GetTimeFieldRangeResponse } from './src/services/types'; export { mlTimefilterRefresh$, type Refresh } from './src/services/timefilter_refresh_service'; diff --git a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx index bfdd1cb43138d..3ea3c091907ee 100644 --- a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx +++ b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx @@ -26,7 +26,10 @@ import type { DataView } from '@kbn/data-plugin/common'; import type { TimefilterContract } from '@kbn/data-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { useDatePickerContext } from '../hooks/use_date_picker_context'; -import { setFullTimeRange } from '../services/full_time_range_selector_service'; +import { + setFullTimeRange, + type SetFullTimeRangeApiPath, +} from '../services/full_time_range_selector_service'; import type { GetTimeFieldRangeResponse } from '../services/types'; import { FROZEN_TIER_PREFERENCE, type FrozenTierPreference } from '../storage'; @@ -64,6 +67,11 @@ export interface FullTimeRangeSelectorProps { * @param value - The time field range response. */ callback?: (value: GetTimeFieldRangeResponse) => void; + /** + * Optional API path. + * @param value - The time field range response. + */ + apiPath?: SetFullTimeRangeApiPath; } /** @@ -83,6 +91,7 @@ export const FullTimeRangeSelector: FC = (props) => query, disabled, callback, + apiPath, } = props; const { http, @@ -98,7 +107,8 @@ export const FullTimeRangeSelector: FC = (props) => toasts, http, query, - frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE + frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE, + apiPath ); if (typeof callback === 'function') { callback(fullTimeRange); @@ -113,7 +123,7 @@ export const FullTimeRangeSelector: FC = (props) => ) ); } - }, [callback, dataView, frozenDataPreference, http, query, timefilter, toasts]); + }, [callback, dataView, frozenDataPreference, http, query, timefilter, toasts, apiPath]); const [isPopoverOpen, setPopover] = useState(false); diff --git a/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts b/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts index ab8d79292fd26..12cd8c724e96d 100644 --- a/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts +++ b/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts @@ -17,6 +17,13 @@ import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils'; import { getTimeFieldRange } from './time_field_range'; import type { GetTimeFieldRangeResponse } from './types'; +/** + * Allowed API paths to be passed to `setFullTimeRange`. + */ +export type SetFullTimeRangeApiPath = + | '/internal/file_upload/time_field_range' + | '/api/ml/fields_service/time_field_range'; + /** * Determines the full available time range of the given Data View and updates * the timefilter accordingly. @@ -27,6 +34,7 @@ import type { GetTimeFieldRangeResponse } from './types'; * @param http - HttpStart * @param query - optional query * @param excludeFrozenData - optional boolean flag + * @param path - optional SetFullTimeRangeApiPath * @returns {GetTimeFieldRangeResponse} */ export async function setFullTimeRange( @@ -35,7 +43,8 @@ export async function setFullTimeRange( toasts: ToastsStart, http: HttpStart, query?: QueryDslQueryContainer, - excludeFrozenData?: boolean + excludeFrozenData?: boolean, + path: SetFullTimeRangeApiPath = '/internal/file_upload/time_field_range' ): Promise { try { const runtimeMappings = dataView.getRuntimeMappings(); @@ -45,6 +54,7 @@ export async function setFullTimeRange( query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query, ...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}), http, + path, }); if (resp.start.epoch && resp.end.epoch) { @@ -52,6 +62,16 @@ export async function setFullTimeRange( from: moment(resp.start.epoch).toISOString(), to: moment(resp.end.epoch).toISOString(), }); + } else if (typeof resp.start === 'number' && typeof resp.end === 'number') { + timefilter.setTime({ + from: moment(resp.start).toISOString(), + to: moment(resp.end).toISOString(), + }); + return { + success: true, + start: { epoch: resp.start, string: moment(resp.start).toISOString() }, + end: { epoch: resp.end, string: moment(resp.end).toISOString() }, + }; } else { toasts.addWarning({ title: i18n.translate('xpack.ml.datePicker.fullTimeRangeSelector.noResults', { diff --git a/x-pack/packages/ml/date_picker/src/services/time_field_range.ts b/x-pack/packages/ml/date_picker/src/services/time_field_range.ts index 3cbf382ecc6c7..117dddc9dfaf3 100644 --- a/x-pack/packages/ml/date_picker/src/services/time_field_range.ts +++ b/x-pack/packages/ml/date_picker/src/services/time_field_range.ts @@ -36,6 +36,10 @@ interface GetTimeFieldRangeOptions { * HTTP client */ http: HttpStart; + /** + * API path ('/internal/file_upload/time_field_range') + */ + path: string; } /** @@ -44,10 +48,10 @@ interface GetTimeFieldRangeOptions { * @returns GetTimeFieldRangeResponse */ export async function getTimeFieldRange(options: GetTimeFieldRangeOptions) { - const { http, ...body } = options; + const { http, path, ...body } = options; return await http.fetch({ - path: `/internal/file_upload/time_field_range`, + path, method: 'POST', body: JSON.stringify(body), }); 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 5da807ddba65d..3fb1877756d15 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 @@ -12,7 +12,7 @@ import { createAlertFactory, getPublicAlertFactory } from '../alert/create_alert import { Alert } from '../alert/alert'; import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock'; import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock'; -import { getAlertsForNotification, processAlerts, setFlapping } from '../lib'; +import { getAlertsForNotification, processAlerts } from '../lib'; import { logAlerts } from '../task_runner/log_alerts'; import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings'; @@ -254,19 +254,6 @@ describe('Legacy Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, }); - expect(setFlapping).toHaveBeenCalledWith( - { - enabled: true, - lookBackWindow: 20, - statusChangeThreshold: 4, - }, - { - '1': new Alert('1', testAlert1), - '2': new Alert('2', testAlert2), - }, - {} - ); - expect(getAlertsForNotification).toHaveBeenCalledWith( { enabled: true, diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 9affe3a67d7eb..8fce6782cd2f1 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -143,12 +143,6 @@ export class LegacyAlertsClient< flappingSettings, }); - setFlapping( - flappingSettings, - processedAlertsActive, - processedAlertsRecovered - ); - const { trimmedAlertsRecovered, earlyRecoveredAlerts } = trimRecoveredAlerts( this.options.logger, processedAlertsRecovered, @@ -213,4 +207,12 @@ export class LegacyAlertsClient< public getExecutorServices() { return getPublicAlertFactory(this.alertFactory!); } + + public setFlapping(flappingSettings: RulesSettingsFlappingProperties) { + setFlapping( + flappingSettings, + this.processedAlerts.active, + this.processedAlerts.recovered + ); + } } diff --git a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts index c69d394d258ec..87af43fe58d2e 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts @@ -38,8 +38,7 @@ const mockAction: RuleAction = { const mockSummaryAction: RuleAction = { id: '1', - // @ts-ignore - group: null, + group: 'default', actionTypeId: 'slack', params: {}, frequency: { diff --git a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts index 2252e38b7c43b..5fa8da9b25d21 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts @@ -69,7 +69,7 @@ export const isSummaryActionThrottled = ({ }; export const generateActionHash = (action: RuleAction) => { - return `${action.actionTypeId}:${action.group || 'summary'}:${ + return `${action.actionTypeId}:${action.frequency?.summary ? 'summary' : action.group}:${ action.frequency?.throttle || 'no-throttling' }`; }; @@ -82,7 +82,9 @@ export const getSummaryActionsFromTaskState = ({ summaryActions?: ThrottledActions; }) => { return Object.entries(summaryActions).reduce((newObj, [key, val]) => { - const actionExists = actions.some((action) => generateActionHash(action) === key); + const actionExists = actions.some( + (action) => action.frequency?.summary && generateActionHash(action) === key + ); if (actionExists) { return { ...newObj, [key]: val }; } else { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 779b467160b2d..7a000815e3959 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -1486,7 +1486,7 @@ describe('Task Runner', () => { generateEnqueueFunctionInput({ isBulk, id: '1', foo: true }) ); expect(result.state.summaryActions).toEqual({ - 'slack:default:1h': { date: new Date(DATE_1970) }, + 'slack:summary:1h': { date: new Date(DATE_1970) }, }); } ); 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 a94512e1be483..82e01a93125e5 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -466,6 +466,8 @@ export class TaskRunner< } }); + this.legacyAlertsClient.setFlapping(flappingSettings); + let alertsToReturn: Record = {}; let recoveredAlertsToReturn: Record = {}; // Only serialize alerts into task state if we're auto-recovering, otherwise diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.ts new file mode 100644 index 0000000000000..cd8777e7ce1d0 --- /dev/null +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.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 * as t from 'io-ts'; + +export const logEcsReformattingRt = t.union([ + t.literal('off'), + t.literal('shade'), + t.literal('replace'), + t.literal('override'), +]); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index a4bc508856b45..c38f4382e7f40 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -24,6 +24,11 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "application_packages", + "type": "text", + "validationName": "string", + }, Object { "key": "capture_body", "options": Array [ @@ -72,6 +77,26 @@ Array [ "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "disable_instrumentations", + "type": "text", + "validationName": "string", + }, + Object { + "key": "disable_outgoing_tracecontext_headers", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "enable_experimental_instrumentations", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "enable_instrumentations", + "type": "text", + "validationName": "string", + }, Object { "key": "enable_log_correlation", "type": "boolean", @@ -98,6 +123,29 @@ Array [ "type": "text", "validationName": "string", }, + Object { + "key": "log_ecs_reformatting", + "options": Array [ + Object { + "text": "off", + "value": "off", + }, + Object { + "text": "shade", + "value": "shade", + }, + Object { + "text": "replace", + "value": "replace", + }, + Object { + "text": "override", + "value": "override", + }, + ], + "type": "select", + "validationName": "(\\"off\\" | \\"shade\\" | \\"replace\\" | \\"override\\")", + }, Object { "key": "log_level", "options": Array [ @@ -133,6 +181,16 @@ Array [ "type": "select", "validationName": "(\\"trace\\" | \\"debug\\" | \\"info\\" | \\"warning\\" | \\"error\\" | \\"critical\\" | \\"off\\")", }, + Object { + "key": "log_sending", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "mongodb_capture_statement_commands", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, Object { "key": "profiling_inferred_spans_enabled", "type": "boolean", @@ -230,6 +288,28 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "span_min_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "span_stack_trace_min_duration", + "min": "-1ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, Object { "key": "stack_trace_limit", "max": undefined, diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 4e0f37fc76f3b..4dc7a6bb35da4 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { captureBodyRt } from '../runtime_types/capture_body_rt'; import { logLevelRt } from '../runtime_types/log_level_rt'; +import { logEcsReformattingRt } from '../runtime_types/log_ecs_reformatting_rt'; import { traceContinuationStrategyRt } from '../runtime_types/trace_continuation_strategy_rt'; import { RawSettingDefinition } from './types'; @@ -93,7 +94,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'The defaults end with a wildcard so that content types like `text/plain; charset=utf-8` are captured as well.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet'], }, // Capture headers @@ -135,6 +136,53 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java'], }, + { + key: 'disable_instrumentations', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.disableInstrumentations.label', + { + defaultMessage: 'Disable instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.disableInstrumentations.description', + { + defaultMessage: + 'Comma-separated list of modules to disable instrumentation for.\n' + + 'When instrumentation is disabled for a module, no spans will be collected for that module.\n' + + '\n' + + 'The up-to-date list of modules for which instrumentation can be disabled is language specific ' + + 'and can be found under the following links: ' + + '[Java](https://www.elastic.co/guide/en/apm/agent/java/current/config-core.html#config-disable-instrumentations)', + } + ), + includeAgents: ['java'], + }, + + { + key: 'disable_outgoing_tracecontext_headers', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate( + 'xpack.apm.agentConfig.disableOutgoingTracecontextHeaders.label', + { + defaultMessage: 'Disable outgoing tracecontext headers', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.disableOutgoingTracecontextHeaders.description', + { + defaultMessage: + 'Use this option to disable `tracecontext` headers injection to any outgoing communication.\n' + + '\n' + + 'WARNING: Disabling `tracecontext` headers injection means that distributed tracing will not work on downstream services.', + } + ), + includeAgents: ['java'], + }, + { key: 'exit_span_min_duration', type: 'duration', @@ -152,7 +200,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'NOTE: If a span propagates distributed tracing ids, it will not be ignored, even if it is shorter than the configured threshold. This is to ensure that no broken traces are recorded.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'nodejs', 'python'], }, { @@ -172,6 +220,32 @@ export const generalSettings: RawSettingDefinition[] = [ 'When set, sends-to and receives-from the specified queues/topic will be ignored.', } ), + includeAgents: ['java', 'dotnet', 'nodejs'], + }, + + { + key: 'log_ecs_reformatting', + validation: logEcsReformattingRt, + type: 'select', + defaultValue: 'off', + label: i18n.translate('xpack.apm.agentConfig.logEcsReformatting.label', { + defaultMessage: 'Log ECS reformatting', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.logEcsReformatting.description', + { + defaultMessage: + 'Specifying whether and how the agent should automatically reformat application logs into ' + + '[ECS-compatible JSON](https://www.elastic.co/guide/en/ecs-logging/overview/master/intro.html), ' + + 'suitable for ingestion into Elasticsearch for further Log analysis.', + } + ), + options: [ + { text: 'off', value: 'off' }, + { text: 'shade', value: 'shade' }, + { text: 'replace', value: 'replace' }, + { text: 'override', value: 'override' }, + ], includeAgents: ['java'], }, @@ -199,6 +273,30 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['dotnet', 'ruby', 'java', 'python', 'nodejs', 'go', 'php'], }, + { + key: 'mongodb_capture_statement_commands', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.mongodbCaptureStatementCommands.label', + { + defaultMessage: 'MongoDB capture statement commands', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.mongodbCaptureStatementCommands.description', + { + defaultMessage: + 'MongoDB command names for which the command document will be captured, limited to common read-only operations by default. ' + + 'Set to `""` (empty) to disable capture, and `*` to capture all (which is discouraged as it may lead to sensitive information capture).\n' + + '\n' + + 'This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. ' + + 'Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.', + } + ), + includeAgents: ['java'], + }, + // Recording { key: 'recording', @@ -250,7 +348,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Span compression reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that some information such as DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, { @@ -271,7 +369,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, { key: 'span_compression_same_kind_max_duration', @@ -291,7 +389,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, // SPAN_FRAMES_MIN_DURATION @@ -307,12 +405,38 @@ export const generalSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.spanFramesMinDuration.description', { defaultMessage: - 'In its default settings, the APM agent will collect a stack trace with every recorded span.\nWhile this is very helpful to find the exact place in your code that causes the span, collecting this stack trace does have some overhead. \nWhen setting this option to a negative value, like `-1ms`, stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n\nTo disable stack trace collection for spans completely, set the value to `0ms`.', + '(Deprecated, use `span_stack_trace_min_duration` instead!) In its default settings, the APM agent will collect a stack trace with every recorded span.\nWhile this is very helpful to find the exact place in your code that causes the span, collecting this stack trace does have some overhead. \nWhen setting this option to a negative value, like `-1ms`, stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n\nTo disable stack trace collection for spans completely, set the value to `0ms`.', } ), excludeAgents: ['js-base', 'rum-js', 'nodejs', 'php'], }, + { + key: 'span_stack_trace_min_duration', + type: 'duration', + min: '-1ms', + defaultValue: '5ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanStackTraceMinDuration.label', + { + defaultMessage: 'Span stack trace minimum duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanStackTraceMinDuration.description', + { + defaultMessage: + 'While this is very helpful to find the exact place in your code that causes the span, ' + + 'collecting this stack trace does have some overhead. When setting this option to the value `0ms`, ' + + 'stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit ' + + 'stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n' + + '\n' + + 'To disable stack trace collection for spans completely, set the value to `-1ms`.', + } + ), + includeAgents: ['java', 'dotnet', 'nodejs', 'python'], + }, + // STACK_TRACE_LIMIT { key: 'stack_trace_limit', @@ -328,7 +452,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Setting it to 0 will disable stack trace collection. Any positive integer value will be used as the maximum number of frames to collect. Setting it -1 means that all frames will be collected.', } ), - includeAgents: ['java', 'dotnet', 'go'], + includeAgents: ['java', 'dotnet', 'go', 'python'], }, { @@ -366,7 +490,7 @@ export const generalSettings: RawSettingDefinition[] = [ { text: 'restart', value: 'restart' }, { text: 'restart_external', value: 'restart_external' }, ], - includeAgents: ['java'], + includeAgents: ['java', 'nodejs', 'python'], }, // Transaction max spans diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index 694eef7885b31..45abfb43edb00 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -9,6 +9,28 @@ import { i18n } from '@kbn/i18n'; import { RawSettingDefinition } from './types'; export const javaSettings: RawSettingDefinition[] = [ + { + key: 'application_packages', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.applicationPackages.label', { + defaultMessage: 'Application packages', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.applicationPackages.description', + { + defaultMessage: + 'Used to determine whether a stack trace frame is an in-app frame or a library frame. ' + + 'This allows the APM app to collapse the stack frames of library code, and highlight the stack frames that originate from your application. ' + + 'Multiple root packages can be set as a comma-separated list; there’s no need to configure sub-packages. ' + + 'Because this setting helps determine which classes to scan on startup, setting this option can also improve startup time.\n' + + '\n' + + 'You must set this option in order to use the API annotations `@CaptureTransaction` and `@CaptureSpan`.', + } + ), + includeAgents: ['java'], + }, + // ENABLE_LOG_CORRELATION { key: 'enable_log_correlation', @@ -47,6 +69,101 @@ export const javaSettings: RawSettingDefinition[] = [ ), includeAgents: ['java'], }, + + { + key: 'enable_experimental_instrumentations', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.enableExperimentalInstrumentations.label', + { + defaultMessage: 'Enable experimental instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.enableExperimentalInstrumentations.description', + { + defaultMessage: + 'Whether to apply experimental instrumentations.\n' + + '\n' + + 'NOTE: Changing this value at runtime can slow down the application temporarily. ' + + 'Setting to true will enable instrumentations in the experimental group.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'enable_instrumentations', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.enableInstrumentations.label', + { + defaultMessage: 'Disable instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.enableInstrumentations.description', + { + defaultMessage: + 'A list of instrumentations which should be selectively enabled. ' + + 'Valid options are listed in the ' + + '[Java APM Agent documentation](https://www.elastic.co/guide/en/apm/agent/java/current/config-core.html#config-disable-instrumentations).\n' + + '\n' + + 'When set to non-empty value, only listed instrumentations will be enabled ' + + 'if they are not disabled through `disable_instrumentations`or `enable_experimental_instrumentations`.\n' + + 'When not set or empty (default), all instrumentations enabled by default will be enabled ' + + 'unless they are disabled through `disable_instrumentations` or `enable_experimental_instrumentations`.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'log_sending', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate('xpack.apm.agentConfig.logSending.label', { + defaultMessage: 'Log sending (experimental)', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.logSending.description', + { + defaultMessage: + 'Experimental, requires latest version of the Java agent.\n' + + '\n' + + 'If set to `true`,\n' + + 'agent will send logs directly to APM server.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'span_min_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate('xpack.apm.agentConfig.spanMinDuration.label', { + defaultMessage: 'Span minimum duration', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.spanMinDuration.description', + { + defaultMessage: + 'Sets the minimum duration of spans. Spans that execute faster than this threshold are attempted to be discarded.\n' + + '\n' + + 'The attempt fails if they lead up to a span that can’t be discarded. Spans that propagate the trace context to ' + + 'downstream services, such as outgoing HTTP requests, can’t be discarded. Additionally, spans that lead to an error ' + + 'or that may be a parent of an async operation can’t be discarded.\n' + + '\n' + + 'However, external calls that don’t propagate context, such as calls to a database, can be discarded using this threshold.', + } + ), + includeAgents: ['java'], + }, + { key: 'stress_monitor_gc_stress_threshold', label: i18n.translate( diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 85c9550fd230d..636c7b2fde323 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -136,12 +136,12 @@ async function createAnomalyDetectionJob({ ], }); - waitForIndexStatus({ + await waitForIndexStatus({ client: esClient, index: '.ml-*', timeout: DEFAULT_TIMEOUT, status: 'yellow', - }); + })(); return anomalyDetectionJob; }); diff --git a/x-pack/plugins/canvas/kibana.jsonc b/x-pack/plugins/canvas/kibana.jsonc index 3ef7326dcd460..c52f6628b4fad 100644 --- a/x-pack/plugins/canvas/kibana.jsonc +++ b/x-pack/plugins/canvas/kibana.jsonc @@ -29,13 +29,16 @@ "presentationUtil", "visualizations", "uiActions", - "share" + "share", + "savedObjectsManagement", + "savedObjectsFinder" ], "optionalPlugins": [ "home", "reporting", "spaces", - "usageCollection" + "usageCollection", + "savedObjects", ], "requiredBundles": [ "discover", @@ -43,9 +46,8 @@ "kibanaUtils", "lens", "maps", - "savedObjects", "visualizations", "fieldFormats" - ] + ], } } diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx index e92c8277488ca..d9c665646e2b4 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx @@ -9,7 +9,7 @@ import React, { FC, useCallback } from 'react'; import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SavedObjectFinderUi, SavedObjectMetaData } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public'; import { useEmbeddablesService, usePlatformService } from '../../services'; const strings = { @@ -38,7 +38,7 @@ export const AddEmbeddableFlyout: FC = ({ const embeddablesService = useEmbeddablesService(); const platformService = usePlatformService(); const { getEmbeddableFactories } = embeddablesService; - const { getHttp, getUISettings } = platformService; + const { getHttp, getUISettings, getSavedObjectsManagement } = platformService; const onAddPanel = useCallback( (id: string, savedObjectType: string) => { @@ -77,13 +77,16 @@ export const AddEmbeddableFlyout: FC = ({ - diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 18facb65202f3..20dee0bf5fa75 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -30,6 +30,7 @@ import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public'; import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { CanvasAppLocatorDefinition } from '../common/locator'; import { SESSIONSTORAGE_LASTPATH, CANVAS_APP } from '../common/lib/constants'; @@ -67,6 +68,7 @@ export interface CanvasStartDeps { presentationUtil: PresentationUtilPluginStart; visualizations: VisualizationsStart; spaces?: SpacesPluginStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } /** diff --git a/x-pack/plugins/canvas/public/services/kibana/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts index 0b51963eeee31..ab5fb175bd6d1 100644 --- a/x-pack/plugins/canvas/public/services/kibana/platform.ts +++ b/x-pack/plugins/canvas/public/services/kibana/platform.ts @@ -46,5 +46,6 @@ export const platformServiceFactory: CanvaPlatformServiceFactory = ({ getSavedObjectsClient: () => coreStart.savedObjects.client, getUISettings: () => coreStart.uiSettings, getHttp: () => coreStart.http, + getSavedObjectsManagement: () => startPlugins.savedObjectsManagement, }; }; diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts index b436f51bb33f9..77649a8fa0592 100644 --- a/x-pack/plugins/canvas/public/services/platform.ts +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -17,6 +17,7 @@ import { } from '@kbn/core/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; export interface CanvasPlatformService { getBasePath: () => string; @@ -39,4 +40,5 @@ export interface CanvasPlatformService { getSavedObjectsClient: () => SavedObjectsClientContract; getUISettings: () => IUiSettingsClient; getHttp: () => HttpStart; + getSavedObjectsManagement: () => SavedObjectsManagementPluginStart; } diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts index d96599257f9b6..cdb75dc96322b 100644 --- a/x-pack/plugins/canvas/public/services/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -37,4 +37,5 @@ export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({ redirectLegacyUrl: noop, getLegacyUrlConflict: undefined, getHttp: noop, + getSavedObjectsManagement: noop, }); diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap index 5c431dee43fe6..f9583adaa84c7 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap +++ b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap @@ -1,11 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` App renders properly 1`] = ` -"
    markdown mock
    markdown mock
    My Canvas Workpad
    " -`; +exports[` App renders properly 1`] = `"
    markdown mock
    markdown mock
    My Canvas Workpad
    "`; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx index b68642d184542..acf71cad3f3ba 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx @@ -59,8 +59,7 @@ const getWrapper: (name?: WorkpadNames) => ReactWrapper = (name = 'hello') => { return mount(); }; -// FLAKY: https://github.com/elastic/kibana/issues/95899 -describe.skip('', () => { +describe('', () => { test('App renders properly', () => { expect(getWrapper().html()).toMatchSnapshot(); }); diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index ef10a58a1e65b..228ae010ddf43 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -45,7 +45,6 @@ "@kbn/kibana-react-plugin", "@kbn/kibana-utils-plugin", "@kbn/presentation-util-plugin", - "@kbn/saved-objects-plugin", "@kbn/ui-actions-plugin", "@kbn/usage-collection-plugin", "@kbn/visualizations-plugin", @@ -79,6 +78,8 @@ "@kbn/shared-ux-router", "@kbn/babel-register", "@kbn/shared-ux-button-toolbar", + "@kbn/saved-objects-finder-plugin", + "@kbn/saved-objects-management-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cases/common/constants/application.ts b/x-pack/plugins/cases/common/constants/application.ts new file mode 100644 index 0000000000000..4b43a17708ab6 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/application.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CASE_VIEW_PAGE_TABS } from '../types'; + +/** + * Application + */ + +export const APP_ID = 'cases' as const; +export const FEATURE_ID = 'generalCases' as const; +export const APP_OWNER = 'cases' as const; +export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; +export const CASES_CREATE_PATH = '/create' as const; +export const CASES_CONFIGURE_PATH = '/configure' as const; +export const CASE_VIEW_PATH = '/:detailName' as const; +export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; +export const CASE_VIEW_ALERT_TABLE_PATH = + `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; +export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; + +/** + * The main Cases application is in the stack management under the + * Alerts and Insights section. To do that, Cases registers to the management + * application. This constant holds the application ID of the management plugin + */ +export const STACK_APP_ID = 'management' as const; diff --git a/x-pack/plugins/cases/common/constants/files.ts b/x-pack/plugins/cases/common/constants/files.ts new file mode 100644 index 0000000000000..7cd7109137976 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/files.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 type { HttpApiTagOperation, Owner } from './types'; + +export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MiB + +export const constructFilesHttpOperationTag = (owner: Owner, operation: HttpApiTagOperation) => { + return `${owner}FilesCases${operation}`; +}; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants/index.ts similarity index 73% rename from x-pack/plugins/cases/common/constants.ts rename to x-pack/plugins/cases/common/constants/index.ts index 601599104641b..f837223e6a07c 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -4,34 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CASE_VIEW_PAGE_TABS } from './types'; -import type { CasesFeaturesAllRequired } from './ui/types'; -export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; -export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; - -/** - * Application - */ +import type { CasesFeaturesAllRequired } from '../ui/types'; -export const APP_ID = 'cases' as const; -export const FEATURE_ID = 'generalCases' as const; -export const APP_OWNER = 'cases' as const; -export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; -export const CASES_CREATE_PATH = '/create' as const; -export const CASES_CONFIGURE_PATH = '/configure' as const; -export const CASE_VIEW_PATH = '/:detailName' as const; -export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; -export const CASE_VIEW_ALERT_TABLE_PATH = - `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; -export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; +export * from './owners'; +export * from './files'; +export * from './application'; -/** - * The main Cases application is in the stack management under the - * Alerts and Insights section. To do that, Cases registers to the management - * application. This constant holds the application ID of the management plugin - */ -export const STACK_APP_ID = 'management' as const; +export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; +export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; /** * Saved objects @@ -111,37 +92,6 @@ export const CONNECTORS_URL = `${ACTION_URL}/connectors` as const; */ export const MAX_ALERTS_PER_CASE = 1000 as const; -/** - * Owner - */ -export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const; -export const OBSERVABILITY_OWNER = 'observability' as const; -export const GENERAL_CASES_OWNER = APP_ID; - -export const OWNER_INFO = { - [SECURITY_SOLUTION_OWNER]: { - id: SECURITY_SOLUTION_OWNER, - appId: 'securitySolutionUI', - label: 'Security', - iconType: 'logoSecurity', - appRoute: '/app/security', - }, - [OBSERVABILITY_OWNER]: { - id: OBSERVABILITY_OWNER, - appId: 'observability-overview', - label: 'Observability', - iconType: 'logoObservability', - appRoute: '/app/observability', - }, - [GENERAL_CASES_OWNER]: { - id: GENERAL_CASES_OWNER, - appId: 'management', - label: 'Stack', - iconType: 'casesApp', - appRoute: '/app/management/insightsAndAlerting', - }, -} as const; - /** * Searching */ @@ -186,6 +136,20 @@ export const UPDATE_CASES_CAPABILITY = 'update_cases' as const; export const DELETE_CASES_CAPABILITY = 'delete_cases' as const; export const PUSH_CASES_CAPABILITY = 'push_cases' as const; +/** + * Cases API Tags + */ + +/** + * This tag registered for the cases suggest user profiles API + */ +export const SUGGEST_USER_PROFILES_API_TAG = 'casesSuggestUserProfiles'; + +/** + * This tag is registered for the security bulk get API + */ +export const BULK_GET_USER_PROFILES_API_TAG = 'bulkGetUserProfiles'; + /** * User profiles */ diff --git a/x-pack/plugins/cases/common/constants/mime_types.ts b/x-pack/plugins/cases/common/constants/mime_types.ts new file mode 100644 index 0000000000000..9f1f455513dab --- /dev/null +++ b/x-pack/plugins/cases/common/constants/mime_types.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * These were retrieved from https://www.iana.org/assignments/media-types/media-types.xhtml#image + */ +const imageMimeTypes = [ + 'image/aces', + 'image/apng', + 'image/avci', + 'image/avcs', + 'image/avif', + 'image/bmp', + 'image/cgm', + 'image/dicom-rle', + 'image/dpx', + 'image/emf', + 'image/example', + 'image/fits', + 'image/g3fax', + 'image/heic', + 'image/heic-sequence', + 'image/heif', + 'image/heif-sequence', + 'image/hej2k', + 'image/hsj2', + 'image/jls', + 'image/jp2', + 'image/jpeg', + 'image/jph', + 'image/jphc', + 'image/jpm', + 'image/jpx', + 'image/jxr', + 'image/jxrA', + 'image/jxrS', + 'image/jxs', + 'image/jxsc', + 'image/jxsi', + 'image/jxss', + 'image/ktx', + 'image/ktx2', + 'image/naplps', + 'image/png', + 'image/prs.btif', + 'image/prs.pti', + 'image/pwg-raster', + 'image/svg+xml', + 'image/t38', + 'image/tiff', + 'image/tiff-fx', + 'image/vnd.adobe.photoshop', + 'image/vnd.airzip.accelerator.azv', + 'image/vnd.cns.inf2', + 'image/vnd.dece.graphic', + 'image/vnd.djvu', + 'image/vnd.dwg', + 'image/vnd.dxf', + 'image/vnd.dvb.subtitle', + 'image/vnd.fastbidsheet', + 'image/vnd.fpx', + 'image/vnd.fst', + 'image/vnd.fujixerox.edmics-mmr', + 'image/vnd.fujixerox.edmics-rlc', + 'image/vnd.globalgraphics.pgb', + 'image/vnd.microsoft.icon', + 'image/vnd.mix', + 'image/vnd.ms-modi', + 'image/vnd.mozilla.apng', + 'image/vnd.net-fpx', + 'image/vnd.pco.b16', + 'image/vnd.radiance', + 'image/vnd.sealed.png', + 'image/vnd.sealedmedia.softseal.gif', + 'image/vnd.sealedmedia.softseal.jpg', + 'image/vnd.svf', + 'image/vnd.tencent.tap', + 'image/vnd.valve.source.texture', + 'image/vnd.wap.wbmp', + 'image/vnd.xiff', + 'image/vnd.zbrush.pcx', + 'image/webp', + 'image/wmf', +]; + +const textMimeTypes = ['text/plain', 'text/csv', 'text/json', 'application/json']; + +const compressionMimeTypes = [ + 'application/zip', + 'application/gzip', + 'application/x-bzip', + 'application/x-bzip2', + 'application/x-7z-compressed', + 'application/x-tar', +]; + +const pdfMimeTypes = ['application/pdf']; + +export const ALLOWED_MIME_TYPES = [ + ...imageMimeTypes, + ...textMimeTypes, + ...compressionMimeTypes, + ...pdfMimeTypes, +]; + +export const IMAGE_MIME_TYPES = new Set(imageMimeTypes); diff --git a/x-pack/plugins/cases/common/constants/owners.ts b/x-pack/plugins/cases/common/constants/owners.ts new file mode 100644 index 0000000000000..60463fa57a976 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/owners.ts @@ -0,0 +1,50 @@ +/* + * 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 { APP_ID } from './application'; +import type { Owner } from './types'; + +/** + * Owner + */ +export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const; +export const OBSERVABILITY_OWNER = 'observability' as const; +export const GENERAL_CASES_OWNER = APP_ID; + +export const OWNERS = [SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER, GENERAL_CASES_OWNER] as const; + +interface RouteInfo { + id: Owner; + appId: string; + label: string; + iconType: string; + appRoute: string; +} + +export const OWNER_INFO: Record = { + [SECURITY_SOLUTION_OWNER]: { + id: SECURITY_SOLUTION_OWNER, + appId: 'securitySolutionUI', + label: 'Security', + iconType: 'logoSecurity', + appRoute: '/app/security', + }, + [OBSERVABILITY_OWNER]: { + id: OBSERVABILITY_OWNER, + appId: 'observability-overview', + label: 'Observability', + iconType: 'logoObservability', + appRoute: '/app/observability', + }, + [GENERAL_CASES_OWNER]: { + id: GENERAL_CASES_OWNER, + appId: 'management', + label: 'Stack', + iconType: 'casesApp', + appRoute: '/app/management/insightsAndAlerting', + }, +} as const; diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts b/x-pack/plugins/cases/common/constants/types.ts similarity index 55% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts rename to x-pack/plugins/cases/common/constants/types.ts index 0a0060606b423..27ee99fa95c9a 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts +++ b/x-pack/plugins/cases/common/constants/types.ts @@ -5,9 +5,12 @@ * 2.0. */ -export interface IndexPatternStats { - indexPatternsWithGeoFieldCount: number; - indexPatternsWithGeoPointFieldCount: number; - indexPatternsWithGeoShapeFieldCount: number; - geoShapeAggLayersCount: number; +import type { OWNERS } from './owners'; + +export enum HttpApiTagOperation { + Read = 'Read', + Create = 'Create', + Delete = 'Delete', } + +export type Owner = typeof OWNERS[number]; diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index 8ca2c23c1eb8d..d41de5543654d 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -55,3 +55,4 @@ export { StatusAll } from './ui/types'; export { getCreateConnectorUrl, getAllConnectorsUrl } from './utils/connectors_api'; export { createUICapabilities } from './utils/capabilities'; +export { getApiTags } from './utils/api_tags'; diff --git a/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap b/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap new file mode 100644 index 0000000000000..ea1ef29e71c59 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`api_tags getApiTags constructs the https tags for the owner: cases 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "casesFilesCasesCreate", + "casesFilesCasesRead", + ], + "delete": Array [ + "casesFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "casesFilesCasesRead", + ], +} +`; + +exports[`api_tags getApiTags constructs the https tags for the owner: observability 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "observabilityFilesCasesCreate", + "observabilityFilesCasesRead", + ], + "delete": Array [ + "observabilityFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "observabilityFilesCasesRead", + ], +} +`; + +exports[`api_tags getApiTags constructs the https tags for the owner: securitySolution 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "securitySolutionFilesCasesCreate", + "securitySolutionFilesCasesRead", + ], + "delete": Array [ + "securitySolutionFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "securitySolutionFilesCasesRead", + ], +} +`; diff --git a/x-pack/plugins/cases/common/utils/api_tags.test.ts b/x-pack/plugins/cases/common/utils/api_tags.test.ts new file mode 100644 index 0000000000000..61e8e335be2cf --- /dev/null +++ b/x-pack/plugins/cases/common/utils/api_tags.test.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 { OWNERS } from '../constants'; +import { getApiTags } from './api_tags'; + +describe('api_tags', () => { + describe('getApiTags', () => { + it.each(OWNERS)('constructs the https tags for the owner: %s', (owner) => { + expect(getApiTags(owner)).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/cases/common/utils/api_tags.ts b/x-pack/plugins/cases/common/utils/api_tags.ts new file mode 100644 index 0000000000000..707188a0fba33 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/api_tags.ts @@ -0,0 +1,26 @@ +/* + * 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 { + BULK_GET_USER_PROFILES_API_TAG, + constructFilesHttpOperationTag, + SUGGEST_USER_PROFILES_API_TAG, +} from '../constants'; +import { HttpApiTagOperation } from '../constants/types'; +import type { Owner } from '../constants/types'; + +export const getApiTags = (owner: Owner) => { + const create = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Create); + const deleteTag = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Delete); + const read = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Read); + + return { + all: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, create, read] as const, + read: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, read] as const, + delete: [deleteTag] as const, + }; +}; diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 37647324ccf7a..2a188586b6389 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -209,7 +209,7 @@ "/s/{spaceId}/api/cases/_find": { "get": { "summary": "Retrieves a paginated subset of cases.", - "operationId": "getCases", + "operationId": "findCases", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "tags": [ "cases" @@ -1201,7 +1201,7 @@ "/s/{spaceId}/api/cases/configure/connectors/_find": { "get": { "summary": "Retrieves information about connectors.", - "operationId": "getCaseConnectors", + "operationId": "findCaseConnectors", "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "cases" @@ -1371,7 +1371,7 @@ "get": { "summary": "Returns the number of cases that are open, closed, and in progress.", "operationId": "getCaseStatus", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "deprecated": true, "tags": [ "cases" @@ -1525,7 +1525,7 @@ { "in": "query", "name": "includeComments", - "description": "Determines whether case comments are returned.", + "description": "Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.", "deprecated": true, "schema": { "type": "boolean", @@ -1806,7 +1806,7 @@ "get": { "summary": "Retrieves all the comments from a case.", "operationId": "getAllCaseComments", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", "deprecated": true, "tags": [ "cases" @@ -2034,7 +2034,7 @@ "/s/{spaceId}/api/cases/{caseId}/user_actions": { "get": { "summary": "Returns all user activity for a case.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", "deprecated": true, "operationId": "getCaseActivity", "tags": [ diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index 8098a2d8787ff..511898bfff1cd 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -124,7 +124,7 @@ paths: /s/{spaceId}/api/cases/_find: get: summary: Retrieves a paginated subset of cases. - operationId: getCases + operationId: findCases description: | You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. tags: @@ -783,7 +783,7 @@ paths: /s/{spaceId}/api/cases/configure/connectors/_find: get: summary: Retrieves information about connectors. - operationId: getCaseConnectors + operationId: findCaseConnectors description: | In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: @@ -889,7 +889,7 @@ paths: summary: Returns the number of cases that are open, closed, and in progress. operationId: getCaseStatus description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. deprecated: true tags: - cases @@ -977,7 +977,7 @@ paths: - $ref: '#/components/parameters/space_id' - in: query name: includeComments - description: Determines whether case comments are returned. + description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. deprecated: true schema: type: boolean @@ -1139,7 +1139,7 @@ paths: summary: Retrieves all the comments from a case. operationId: getAllCaseComments description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. deprecated: true tags: - cases @@ -1263,7 +1263,7 @@ paths: get: summary: Returns all user activity for a case. description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. deprecated: true operationId: getCaseActivity tags: diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml index f64fdbf0b9bf8..6e61d14a27110 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml @@ -1,6 +1,6 @@ get: summary: Retrieves a paginated subset of cases. - operationId: getCases + operationId: findCases description: > You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml index 7cdd1bf63ae61..bfe60072b8f50 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml @@ -1,6 +1,6 @@ get: summary: Retrieves information about connectors. - operationId: getCaseConnectors + operationId: findCaseConnectors description: > In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml index a9db413e2eaef..d3c4b40fa3aea 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml @@ -2,6 +2,7 @@ get: summary: Returns the number of cases that are open, closed, and in progress. operationId: getCaseStatus description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml index aca09f4a74420..856a5dc24096f 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml @@ -12,7 +12,7 @@ get: - $ref: '../components/parameters/space_id.yaml' - in: query name: includeComments - description: Determines whether case comments are returned. + description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. deprecated: true schema: type: boolean diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml index 1b69926377ffb..9c50a70619b41 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml @@ -111,6 +111,8 @@ get: summary: Retrieves all the comments from a case. operationId: getAllCaseComments description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; + instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml index a21988cd5434f..4aa52bdbc44b5 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml @@ -1,6 +1,7 @@ get: summary: Returns all user activity for a case. description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. diff --git a/x-pack/plugins/cases/kibana.jsonc b/x-pack/plugins/cases/kibana.jsonc index 94263c383745f..afed1cd7631f8 100644 --- a/x-pack/plugins/cases/kibana.jsonc +++ b/x-pack/plugins/cases/kibana.jsonc @@ -25,7 +25,8 @@ "management", "security", "notifications", - "ruleRegistry" + "ruleRegistry", + "files", ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx index a06235d144a50..88d94f65f900c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx @@ -186,7 +186,12 @@ const ActionColumnComponent: React.FC<{ theCase: Case; disableActions: boolean } panelPaddingSize="none" anchorPosition="downLeft" > - + {deleteAction.isModalVisible ? ( { + return { + id: owner, + allowedMimeTypes: ALLOWED_MIME_TYPES, + maxSizeBytes: MAX_FILE_SIZE, + }; +}; + +/** + * The file kind definition for interacting with the file service for the UI + */ +const CASES_FILE_KINDS: Record = { + [APP_ID]: buildFileKind(APP_ID), + [SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER), + [OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER), +}; + +export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => { + for (const fileKind of Object.values(CASES_FILE_KINDS)) { + filesSetupPlugin.registerFileKind(fileKind); + } +}; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 51f2ae92e3094..83b0f2fb0f009 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -27,6 +27,7 @@ import { groupAlertsByRule } from './client/helpers/group_alerts_by_rule'; import { getUICapabilities } from './client/helpers/capabilities'; import { ExternalReferenceAttachmentTypeRegistry } from './client/attachment_framework/external_reference_registry'; import { PersistableStateAttachmentTypeRegistry } from './client/attachment_framework/persistable_state_registry'; +import { registerCaseFileKinds } from './files'; /** * @public @@ -52,6 +53,8 @@ export class CasesUiPlugin const externalReferenceAttachmentTypeRegistry = this.externalReferenceAttachmentTypeRegistry; const persistableStateAttachmentTypeRegistry = this.persistableStateAttachmentTypeRegistry; + registerCaseFileKinds(plugins.files); + if (plugins.home) { plugins.home.featureCatalogue.register({ id: APP_ID, diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 732fcfee5f0d6..f430b99ae17f2 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -22,6 +22,7 @@ import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } fr import type { DistributiveOmit } from '@elastic/eui'; import type { ApmBase } from '@elastic/apm-rum'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import type { FilesSetup, FilesStart } from '@kbn/files-plugin/public'; import type { CasesByAlertId, CasesByAlertIDRequest, @@ -50,6 +51,7 @@ import type { ExternalReferenceAttachmentTypeRegistry } from './client/attachmen import type { PersistableStateAttachmentTypeRegistry } from './client/attachment_framework/persistable_state_registry'; export interface CasesPluginSetup { + files: FilesSetup; security: SecurityPluginSetup; management: ManagementSetup; home?: HomePublicPluginSetup; @@ -58,6 +60,7 @@ export interface CasesPluginSetup { export interface CasesPluginStart { data: DataPublicPluginStart; embeddable: EmbeddableStart; + files: FilesStart; licensing?: LicensingPluginStart; lens: LensPublicStart; storage: Storage; diff --git a/x-pack/plugins/cases/server/features.ts b/x-pack/plugins/cases/server/features.ts index 2573e5f58b3f3..05ee00cb1b037 100644 --- a/x-pack/plugins/cases/server/features.ts +++ b/x-pack/plugins/cases/server/features.ts @@ -8,10 +8,11 @@ import { i18n } from '@kbn/i18n'; import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { APP_ID, FEATURE_ID } from '../common/constants'; -import { createUICapabilities } from '../common'; +import { createUICapabilities, getApiTags } from '../common'; /** * The order of appearance in the feature privilege page @@ -23,6 +24,7 @@ const FEATURE_ORDER = 3100; export const getCasesKibanaFeature = (): KibanaFeatureConfig => { const capabilities = createUICapabilities(); + const apiTags = getApiTags(APP_ID); return { id: FEATURE_ID, @@ -38,7 +40,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { cases: [APP_ID], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: apiTags.all, cases: { create: [APP_ID], read: [APP_ID], @@ -49,13 +51,13 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { insightsAndAlerting: [APP_ID], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: capabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: apiTags.read, cases: { read: [APP_ID], }, @@ -64,7 +66,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: capabilities.read, }, @@ -79,7 +81,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { groupType: 'independent', privileges: [ { - api: [], + api: apiTags.delete, id: 'cases_delete', name: i18n.translate('xpack.cases.features.deleteSubFeatureDetails', { defaultMessage: 'Delete cases and comments', diff --git a/x-pack/plugins/cases/server/files/index.ts b/x-pack/plugins/cases/server/files/index.ts new file mode 100644 index 0000000000000..fb17fd50fa870 --- /dev/null +++ b/x-pack/plugins/cases/server/files/index.ts @@ -0,0 +1,71 @@ +/* + * 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 { FileJSON, FileKind } from '@kbn/files-plugin/common'; +import type { FilesSetup } from '@kbn/files-plugin/server'; +import { + APP_ID, + constructFilesHttpOperationTag, + MAX_FILE_SIZE, + OBSERVABILITY_OWNER, + SECURITY_SOLUTION_OWNER, +} from '../../common/constants'; +import type { Owner } from '../../common/constants/types'; +import { HttpApiTagOperation } from '../../common/constants/types'; +import { ALLOWED_MIME_TYPES, IMAGE_MIME_TYPES } from '../../common/constants/mime_types'; + +const buildFileKind = (owner: Owner): FileKind => { + return { + id: owner, + http: fileKindHttpTags(owner), + maxSizeBytes, + allowedMimeTypes: ALLOWED_MIME_TYPES, + }; +}; + +const fileKindHttpTags = (owner: Owner): FileKind['http'] => { + return { + create: buildTag(owner, HttpApiTagOperation.Create), + delete: buildTag(owner, HttpApiTagOperation.Delete), + download: buildTag(owner, HttpApiTagOperation.Read), + getById: buildTag(owner, HttpApiTagOperation.Read), + list: buildTag(owner, HttpApiTagOperation.Read), + }; +}; + +const access = 'access:'; + +const buildTag = (owner: Owner, operation: HttpApiTagOperation) => { + return { + tags: [`${access}${constructFilesHttpOperationTag(owner, operation)}`], + }; +}; + +const MAX_IMAGE_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB + +const maxSizeBytes = (file: FileJSON): number => { + if (file.mimeType != null && IMAGE_MIME_TYPES.has(file.mimeType)) { + return MAX_IMAGE_FILE_SIZE; + } + + return MAX_FILE_SIZE; +}; + +/** + * The file kind definition for interacting with the file service for the backend + */ +const CASES_FILE_KINDS: Record = { + [APP_ID]: buildFileKind(APP_ID), + [SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER), + [OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER), +}; + +export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => { + for (const fileKind of Object.values(CASES_FILE_KINDS)) { + filesSetupPlugin.registerFileKind(fileKind); + } +}; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index a5a1d728311d4..8d2036921d84b 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -14,6 +14,7 @@ import type { CoreStart, } from '@kbn/core/server'; +import type { FilesSetup } from '@kbn/files-plugin/server'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { PluginSetupContract as ActionsPluginSetup, @@ -56,11 +57,13 @@ import { PersistableStateAttachmentTypeRegistry } from './attachment_framework/p import { ExternalReferenceAttachmentTypeRegistry } from './attachment_framework/external_reference_registry'; import { UserProfileService } from './services'; import { LICENSING_CASE_ASSIGNMENT_FEATURE } from './common/constants'; +import { registerCaseFileKinds } from './files'; export interface PluginsSetup { actions: ActionsPluginSetup; lens: LensServerPluginSetup; features: FeaturesPluginSetup; + files: FilesSetup; security: SecurityPluginSetup; licensing: LicensingPluginSetup; taskManager?: TaskManagerSetupContract; @@ -104,6 +107,8 @@ export class CasePlugin { )}] and plugins [${Object.keys(plugins)}]` ); + registerCaseFileKinds(plugins.files); + this.securityPluginSetup = plugins.security; this.lensEmbeddableFactory = plugins.lens.lensEmbeddableFactory; diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index a1737094134f2..5ba7e85918975 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -56,6 +56,8 @@ "@kbn/core-saved-objects-base-server-mocks", "@kbn/core-saved-objects-utils-server", "@kbn/shared-ux-router", + "@kbn/files-plugin", + "@kbn/shared-ux-file-types", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_defend/README.md b/x-pack/plugins/cloud_defend/README.md index c0175af9cc2a6..9df1a13d328d2 100755 --- a/x-pack/plugins/cloud_defend/README.md +++ b/x-pack/plugins/cloud_defend/README.md @@ -42,6 +42,7 @@ responses: ``` node scripts/type_check.js --project x-pack/plugins/cloud_defend/tsconfig.json +node scripts/eslint.js x-pack/plugins/cloud_defend yarn test:jest x-pack/plugins/cloud_defend ``` diff --git a/x-pack/plugins/cloud_defend/common/constants.ts b/x-pack/plugins/cloud_defend/common/constants.ts index 860f3d4adffea..8fc54773da295 100755 --- a/x-pack/plugins/cloud_defend/common/constants.ts +++ b/x-pack/plugins/cloud_defend/common/constants.ts @@ -4,9 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; export const PLUGIN_ID = 'cloudDefend'; -export const PLUGIN_NAME = 'cloudDefend'; +export const PLUGIN_NAME = 'Cloud Defend'; export const INTEGRATION_PACKAGE_NAME = 'cloud_defend'; export const INPUT_CONTROL = 'cloud_defend/control'; export const ALERTS_DATASET = 'cloud_defend.alerts'; +export const ALERTS_INDEX_PATTERN = 'cloud_defend.alerts*'; + +export const POLICIES_ROUTE_PATH = '/internal/cloud_defend/policies'; +export const STATUS_ROUTE_PATH = '/internal/cloud_defend/status'; + +export const CLOUD_DEFEND_FLEET_PACKAGE_KUERY = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${INTEGRATION_PACKAGE_NAME}`; diff --git a/x-pack/plugins/cloud_defend/common/schemas/policy.ts b/x-pack/plugins/cloud_defend/common/schemas/policy.ts new file mode 100644 index 0000000000000..c6fff63b6bb81 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/schemas/policy.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 { type TypeOf, schema } from '@kbn/config-schema'; + +export const DEFAULT_POLICIES_PER_PAGE = 20; +export const POLICIES_PACKAGE_POLICY_PREFIX = 'package_policy.'; +export const policiesQueryParamsSchema = schema.object({ + /** + * The page of objects to return + */ + page: schema.number({ defaultValue: 1, min: 1 }), + /** + * The number of objects to include in each page + */ + per_page: schema.number({ defaultValue: DEFAULT_POLICIES_PER_PAGE, min: 0 }), + /** + * Once of PackagePolicy fields for sorting the found objects. + * Sortable fields: + * - package_policy.id + * - package_policy.name + * - package_policy.policy_id + * - package_policy.namespace + * - package_policy.updated_at + * - package_policy.updated_by + * - package_policy.created_at + * - package_policy.created_by, + * - package_policy.package.name + * - package_policy.package.title + * - package_policy.package.version + */ + sort_field: schema.oneOf( + [ + schema.literal('package_policy.id'), + schema.literal('package_policy.name'), + schema.literal('package_policy.policy_id'), + schema.literal('package_policy.namespace'), + schema.literal('package_policy.updated_at'), + schema.literal('package_policy.updated_by'), + schema.literal('package_policy.created_at'), + schema.literal('package_policy.created_by'), + schema.literal('package_policy.package.name'), + schema.literal('package_policy.package.title'), + ], + { defaultValue: 'package_policy.name' } + ), + /** + * The order to sort by + */ + sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + /** + * Policy filter + */ + policy_name: schema.maybe(schema.string()), +}); + +export type PoliciesQueryParams = TypeOf; diff --git a/x-pack/plugins/cloud_defend/common/types.ts b/x-pack/plugins/cloud_defend/common/types.ts new file mode 100644 index 0000000000000..c3bdbb4418183 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/types.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 type { PackagePolicy, AgentPolicy } from '@kbn/fleet-plugin/common'; + +export type IndexStatus = + | 'not-empty' // Index contains documents + | 'empty' // Index doesn't contain documents (or doesn't exist) + | 'unprivileged'; // User doesn't have access to query the index + +export type CloudDefendStatusCode = + | 'indexed' // alerts index exists and has results + | 'indexing' // index timeout was not surpassed since installation, assumes data is being indexed + | 'unprivileged' // user lacks privileges for the alerts index + | 'index-timeout' // index timeout was surpassed since installation + | 'not-deployed' // no healthy agents were deployed + | 'not-installed'; // number of installed integrations is 0; + +export interface IndexDetails { + index: string; + status: IndexStatus; +} + +interface BaseCloudDefendSetupStatus { + indicesDetails: IndexDetails[]; + latestPackageVersion: string; + installedPackagePolicies: number; + healthyAgents: number; +} + +interface CloudDefendSetupNotInstalledStatus extends BaseCloudDefendSetupStatus { + status: Extract; +} + +interface CloudDefendSetupInstalledStatus extends BaseCloudDefendSetupStatus { + status: Exclude; + // status can be `indexed` but return with undefined package information in this case + installedPackageVersion: string | undefined; +} + +export type CloudDefendSetupStatus = + | CloudDefendSetupInstalledStatus + | CloudDefendSetupNotInstalledStatus; + +export type AgentPolicyStatus = Pick & { agents: number }; + +export interface CloudDefendPolicy { + package_policy: PackagePolicy; + agent_policy: AgentPolicyStatus; +} diff --git a/x-pack/plugins/cloud_defend/common/utils/helpers.ts b/x-pack/plugins/cloud_defend/common/utils/helpers.ts new file mode 100644 index 0000000000000..14b506e8d2e70 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/helpers.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 { Truthy } from 'lodash'; +import { INTEGRATION_PACKAGE_NAME } from '../constants'; + +/** + * @example + * declare const foo: Array + * foo.filter(isNonNullable) // foo is Array + */ +export const isNonNullable = (v: T): v is NonNullable => + v !== null && v !== undefined; + +export const truthy = (value: T): value is Truthy => !!value; + +export const extractErrorMessage = (e: unknown, defaultMessage = 'Unknown Error'): string => { + if (e instanceof Error) return e.message; + if (typeof e === 'string') return e; + + return defaultMessage; // TODO: i18n +}; + +export function assert(condition: any, msg?: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +export const isCloudDefendPackage = (packageName?: string) => + packageName === INTEGRATION_PACKAGE_NAME; diff --git a/x-pack/plugins/cloud_defend/common/utils/subscription.test.ts b/x-pack/plugins/cloud_defend/common/utils/subscription.test.ts new file mode 100644 index 0000000000000..e47b887ae520f --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/subscription.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LicenseType } from '@kbn/licensing-plugin/common/types'; +import { isSubscriptionAllowed } from './subscription'; +import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; + +const ON_PREM_ALLOWED_LICENSES: readonly LicenseType[] = ['enterprise', 'trial']; +const ON_PREM_NOT_ALLOWED_LICENSES: readonly LicenseType[] = ['basic', 'gold', 'platinum']; +const ALL_LICENSE_TYPES: readonly LicenseType[] = [ + 'standard', + ...ON_PREM_NOT_ALLOWED_LICENSES, + ...ON_PREM_NOT_ALLOWED_LICENSES, +]; + +describe('isSubscriptionAllowed', () => { + it('should allow any cloud subscription', () => { + const isCloudEnabled = true; + ALL_LICENSE_TYPES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should not allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_NOT_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/common/utils/subscription.ts b/x-pack/plugins/cloud_defend/common/utils/subscription.ts new file mode 100644 index 0000000000000..e54eb0c4d4581 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/subscription.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 type { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types'; +import { PLUGIN_NAME } from '../constants'; + +const MINIMUM_NON_CLOUD_LICENSE_TYPE: LicenseType = 'enterprise'; + +export const isSubscriptionAllowed = (isCloudEnabled?: boolean, license?: ILicense): boolean => { + if (isCloudEnabled) { + return true; + } + + if (!license) { + return false; + } + + const licenseCheck = license.check(PLUGIN_NAME, MINIMUM_NON_CLOUD_LICENSE_TYPE); + return licenseCheck.state === 'valid'; +}; diff --git a/x-pack/plugins/cloud_defend/kibana.jsonc b/x-pack/plugins/cloud_defend/kibana.jsonc index f561c33a5f832..392724467fd70 100644 --- a/x-pack/plugins/cloud_defend/kibana.jsonc +++ b/x-pack/plugins/cloud_defend/kibana.jsonc @@ -2,14 +2,31 @@ "type": "plugin", "id": "@kbn/cloud-defend-plugin", "owner": "@elastic/sec-cloudnative-integrations", - "description": "Defend for Containers", + "description": "Defend for containers (D4C)", "plugin": { "id": "cloudDefend", - "server": false, + "server": true, "browser": true, + "configPath": [ + "xpack", + "cloudDefend" + ], "requiredPlugins": [ + "navigation", + "data", "fleet", - "kibanaReact" + "unifiedSearch", + "kibanaReact", + "cloud", + "security", + "licensing" + ], + "optionalPlugins": [ + "usageCollection" + ], + "requiredBundles": [ + "kibanaReact", + "usageCollection" ] } } diff --git a/x-pack/plugins/cloud_defend/public/application/route.tsx b/x-pack/plugins/cloud_defend/public/application/route.tsx new file mode 100644 index 0000000000000..27959ec0845e5 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/route.tsx @@ -0,0 +1,50 @@ +/* + * 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 { Route } from '@kbn/shared-ux-router'; +import { type RouteProps } from 'react-router-dom'; +import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; +import { cloudDefendPages } from '../common/navigation/constants'; +import { useSecuritySolutionContext } from './security_solution_context'; +import type { CloudDefendPageNavigationItem } from '../common/navigation/types'; + +type CloudDefendRouteProps = Omit & CloudDefendPageNavigationItem; + +// Security SpyRoute can be automatically rendered for pages with static paths, Security will manage everything using the `links` object. +// Pages with dynamic paths are not in the Security `links` object, they must render SpyRoute with the parameters values, if needed. +const STATIC_PATH_PAGE_IDS = Object.fromEntries( + Object.values(cloudDefendPages).map(({ id }) => [id, true]) +); + +export const CloudDefendRoute: React.FC = ({ + id, + children, + component: Component, + disabled = false, + ...cloudDefendRouteProps +}) => { + const SpyRoute = useSecuritySolutionContext()?.getSpyRouteComponent(); + + if (disabled) { + return null; + } + + const routeProps: RouteProps = { + ...cloudDefendRouteProps, + ...(Component && { + render: (renderProps) => ( + + {STATIC_PATH_PAGE_IDS[id] && SpyRoute && } + + + ), + }), + }; + + return {children}; +}; diff --git a/x-pack/plugins/cloud_defend/public/application/router.test.tsx b/x-pack/plugins/cloud_defend/public/application/router.test.tsx new file mode 100644 index 0000000000000..af7766a290fca --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/router.test.tsx @@ -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 CloudDefendRouter from './router'; +import React from 'react'; +import { render } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import type { CloudDefendPage, CloudDefendPageNavigationItem } from '../common/navigation/types'; +import { CloudDefendSecuritySolutionContext } from '../types'; +import { createMemoryHistory, MemoryHistory } from 'history'; +import * as constants from '../common/navigation/constants'; +import { QueryClientProviderProps } from '@tanstack/react-query'; + +jest.mock('../pages/policies', () => ({ + Policies: () =>
    Policies
    , +})); + +jest.mock('@tanstack/react-query', () => ({ + QueryClientProvider: ({ children }: QueryClientProviderProps) => <>{children}, + QueryClient: jest.fn(), +})); + +describe('CloudDefendRouter', () => { + const originalCloudDefendPages = { ...constants.cloudDefendPages }; + const mockConstants = constants as { + cloudDefendPages: Record; + }; + + const securityContext: CloudDefendSecuritySolutionContext = { + getFiltersGlobalComponent: jest.fn(), + getSpyRouteComponent: () => () =>
    , + }; + + let history: MemoryHistory; + + const renderCloudDefendRouter = () => + render( + + + + ); + + beforeEach(() => { + mockConstants.cloudDefendPages = originalCloudDefendPages; + jest.clearAllMocks(); + history = createMemoryHistory(); + }); + + describe('happy path', () => { + it('should render Policies', () => { + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('Policies')).toBeInTheDocument(); + }); + }); + + describe('unhappy path', () => { + it('should redirect base path to policies', () => { + history.push('/cloud_defend/some_wrong_path'); + const result = renderCloudDefendRouter(); + + expect(history.location.pathname).toEqual('/cloud_defend/policies'); + expect(result.queryByTestId('Policies')).toBeInTheDocument(); + }); + }); + + describe('CloudDefendRoute', () => { + it('should not render disabled path', () => { + mockConstants.cloudDefendPages = { + ...constants.cloudDefendPages, + policies: { + ...constants.cloudDefendPages.policies, + disabled: true, + }, + }; + + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('Policies')).not.toBeInTheDocument(); + }); + + it('should render SpyRoute for static paths', () => { + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('mockedSpyRoute')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/application/router.tsx b/x-pack/plugins/cloud_defend/public/application/router.tsx new file mode 100644 index 0000000000000..3fa261d35c765 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/router.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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Redirect, Switch } from 'react-router-dom'; +import { Route } from '@kbn/shared-ux-router'; +import { cloudDefendPages } from '../common/navigation/constants'; +import type { CloudDefendSecuritySolutionContext } from '../types'; +import { SecuritySolutionContext } from './security_solution_context'; +import { Policies } from '../pages/policies'; +import { CloudDefendRoute } from './route'; + +const queryClient = new QueryClient({ + defaultOptions: { queries: { refetchOnWindowFocus: false } }, +}); + +export interface CloudDefendRouterProps { + securitySolutionContext?: CloudDefendSecuritySolutionContext; +} + +export const CloudDefendRouter = ({ securitySolutionContext }: CloudDefendRouterProps) => { + const routerElement = ( + + + + + + + + + + ); + + if (securitySolutionContext) { + return ( + + {routerElement} + + ); + } + + return <>{routerElement}; +}; + +// Using a default export for usage with `React.lazy` +// eslint-disable-next-line import/no-default-export +export { CloudDefendRouter as default }; diff --git a/x-pack/plugins/cloud_defend/public/application/security_solution_context.ts b/x-pack/plugins/cloud_defend/public/application/security_solution_context.ts new file mode 100644 index 0000000000000..fcbd10057021c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/security_solution_context.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useContext } from 'react'; +import type { CloudDefendSecuritySolutionContext } from '../types'; + +export const SecuritySolutionContext = React.createContext< + CloudDefendSecuritySolutionContext | undefined +>(undefined); + +export const useSecuritySolutionContext = () => { + return useContext(SecuritySolutionContext); +}; diff --git a/x-pack/plugins/cloud_defend/public/application/setup_context.ts b/x-pack/plugins/cloud_defend/public/application/setup_context.ts new file mode 100644 index 0000000000000..574404ace38ce --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/setup_context.ts @@ -0,0 +1,16 @@ +/* + * 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 { createContext } from 'react'; + +interface SetupContextValue { + isCloudEnabled?: boolean; +} + +/** + * A utility to pass data from the plugin setup lifecycle stage to application components + */ +export const SetupContext = createContext({}); diff --git a/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg b/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg new file mode 100644 index 0000000000000..a0534292eb717 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx b/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx new file mode 100644 index 0000000000000..c41ffdab0851c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx @@ -0,0 +1,26 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { + epmRouteService, + type GetInfoResponse, + type DefaultPackagesInstallationError, +} from '@kbn/fleet-plugin/common'; +import { INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { useKibana } from '../hooks/use_kibana'; + +/** + * This hook will find our integration and return its PackageInfo + * */ +export const useCloudDefendIntegration = () => { + const { http } = useKibana().services; + + return useQuery(['integrations'], () => + http.get(epmRouteService.getInfoPath(INTEGRATION_PACKAGE_NAME)) + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts b/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts new file mode 100644 index 0000000000000..2d07d138e8d92 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import { useKibana } from '../hooks/use_kibana'; +import { CloudDefendSetupStatus } from '../../../common/types'; +import { STATUS_ROUTE_PATH } from '../../../common/constants'; + +const getCloudDefendSetupStatusQueryKey = 'cloud_defend_status_key'; + +export const useCloudDefendSetupStatusApi = () => { + const { http } = useKibana().services; + return useQuery( + [getCloudDefendSetupStatusQueryKey], + () => http.get(STATUS_ROUTE_PATH) + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/constants.ts b/x-pack/plugins/cloud_defend/public/common/constants.ts index d0baec8804ff7..1e101a47c0122 100644 --- a/x-pack/plugins/cloud_defend/public/common/constants.ts +++ b/x-pack/plugins/cloud_defend/public/common/constants.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 10; // generic default # of table rows to show (currently we only have a list of policies) +export const LOCAL_STORAGE_PAGE_SIZE = 'cloudDefend:userPageSize'; export const VALID_SELECTOR_NAME_REGEX = /^[a-z0-9][a-z0-9_\-]+$/i; // alphanumberic (no - or _ allowed on first char) export const MAX_SELECTOR_NAME_LENGTH = 128; // chars export const MAX_CONDITION_VALUE_LENGTH_BYTES = 511; diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.ts new file mode 100644 index 0000000000000..261afc07db00f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.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 type { CoreStart } from '@kbn/core/public'; +import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public'; +import type { CloudDefendPluginStartDeps } from '../../types'; + +type CloudDefendKibanaContext = CoreStart & CloudDefendPluginStartDeps; + +export const useKibana = () => useKibanaBase(); diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts new file mode 100644 index 0000000000000..314dfbe661d93 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts @@ -0,0 +1,26 @@ +/* + * 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 useLocalStorage from 'react-use/lib/useLocalStorage'; +import { DEFAULT_VISIBLE_ROWS_PER_PAGE } from '../constants'; + +/** + * @description handles persisting the users table row size selection + */ +export const usePageSize = (localStorageKey: string) => { + const [persistedPageSize, setPersistedPageSize] = useLocalStorage( + localStorageKey, + DEFAULT_VISIBLE_ROWS_PER_PAGE + ); + + let pageSize: number = DEFAULT_VISIBLE_ROWS_PER_PAGE; + + if (persistedPageSize) { + pageSize = persistedPageSize; + } + + return { pageSize, setPageSize: setPersistedPageSize }; +}; diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.ts new file mode 100644 index 0000000000000..26dd4caa33c4d --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.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 { useContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { SetupContext } from '../../application/setup_context'; +import { isSubscriptionAllowed } from '../../../common/utils/subscription'; +import { useKibana } from './use_kibana'; + +const SUBSCRIPTION_QUERY_KEY = 'cloud_defend_subscription_query_key'; + +export const useSubscriptionStatus = () => { + const { licensing } = useKibana().services; + const { isCloudEnabled } = useContext(SetupContext); + return useQuery([SUBSCRIPTION_QUERY_KEY], async () => { + const license = await licensing.refresh(); + return isSubscriptionAllowed(isCloudEnabled, license); + }); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/constants.ts b/x-pack/plugins/cloud_defend/public/common/navigation/constants.ts new file mode 100644 index 0000000000000..b166184dddb87 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/constants.ts @@ -0,0 +1,26 @@ +/* + * 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 type { CloudDefendPage, CloudDefendPageNavigationItem } from './types'; + +const NAV_ITEMS_NAMES = { + POLICIES: i18n.translate('xpack.cloudDefend.navigation.policiesNavItemLabel', { + defaultMessage: 'Defend for containers (D4C)', + }), +}; + +/** The base path for all cloud defend pages. */ +export const CLOUD_DEFEND_BASE_PATH = '/cloud_defend'; + +export const cloudDefendPages: Record = { + policies: { + name: NAV_ITEMS_NAMES.POLICIES, + path: `${CLOUD_DEFEND_BASE_PATH}/policies`, + id: 'cloud_defend-policies', + }, +}; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts new file mode 100644 index 0000000000000..f6cfe42d0583a --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cloudDefendPages } from './constants'; +import { getSecuritySolutionLink, getSecuritySolutionNavTab } from './security_solution_links'; +import { Chance } from 'chance'; +import type { CloudDefendPage } from './types'; + +const chance = new Chance(); + +describe('getSecuritySolutionLink', () => { + it('gets the correct link properties', () => { + const cloudDefendPage = chance.pickone(['policies']); + + const link = getSecuritySolutionLink(cloudDefendPage); + + expect(link.id).toEqual(cloudDefendPages[cloudDefendPage].id); + expect(link.path).toEqual(cloudDefendPages[cloudDefendPage].path); + expect(link.title).toEqual(cloudDefendPages[cloudDefendPage].name); + }); +}); + +describe('getSecuritySolutionNavTab', () => { + it('gets the correct nav tab properties', () => { + const cloudDefendPage = chance.pickone(['policies']); + const basePath = chance.word(); + + const navTab = getSecuritySolutionNavTab(cloudDefendPage, basePath); + + expect(navTab.id).toEqual(cloudDefendPages[cloudDefendPage].id); + expect(navTab.name).toEqual(cloudDefendPages[cloudDefendPage].name); + expect(navTab.href).toEqual(`${basePath}${cloudDefendPages[cloudDefendPage].path}`); + expect(navTab.disabled).toEqual(!!cloudDefendPages[cloudDefendPage].disabled); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts new file mode 100644 index 0000000000000..58e816c135593 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.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 { cloudDefendPages } from './constants'; +import type { CloudDefendPageId, CloudDefendPage } from './types'; + +interface CloudDefendLinkItem { + id: TId; + title: string; + path: string; +} + +interface CloudDefendNavTab { + id: TId; + name: string; + href: string; + disabled: boolean; +} + +/** + * Gets the cloud_defend link properties of a Cloud Defend page for navigation in the security solution. + * @param cloudDefendPage the name of the cloud defend page. + */ +export const getSecuritySolutionLink = ( + cloudDefendPage: CloudDefendPage +): CloudDefendLinkItem => { + return { + id: cloudDefendPages[cloudDefendPage].id as TId, + title: cloudDefendPages[cloudDefendPage].name, + path: cloudDefendPages[cloudDefendPage].path, + }; +}; + +/** + * Gets the link properties of a Cloud Defend page for navigation in the old security solution navigation. + * @param cloudDefendPage the name of the cloud defend page. + * @param basePath the base path for links. + */ +export const getSecuritySolutionNavTab = ( + cloudDefendPage: CloudDefendPage, + basePath: string +): CloudDefendNavTab => ({ + id: cloudDefendPages[cloudDefendPage].id as TId, + name: cloudDefendPages[cloudDefendPage].name, + href: `${basePath}${cloudDefendPages[cloudDefendPage].path}`, + disabled: !!cloudDefendPages[cloudDefendPage].disabled, +}); diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/types.ts b/x-pack/plugins/cloud_defend/public/common/navigation/types.ts new file mode 100644 index 0000000000000..56da6ac4c3948 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export interface CloudDefendNavigationItem { + readonly name: string; + readonly path: string; + readonly disabled?: boolean; +} + +export interface CloudDefendPageNavigationItem extends CloudDefendNavigationItem { + id: CloudDefendPageId; +} + +export type CloudDefendPage = 'policies'; + +/** + * All the IDs for the cloud defend pages. + * This needs to match the cloud defend page entries in `SecurityPageName` in `x-pack/plugins/security_solution/common/constants.ts`. + */ +export type CloudDefendPageId = 'cloud_defend-policies'; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts b/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts new file mode 100644 index 0000000000000..b2d0cca31fcba --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts @@ -0,0 +1,50 @@ +/* + * 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 { pagePathGetters, pkgKeyFromPackageInfo } from '@kbn/fleet-plugin/public'; +import { INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { useCloudDefendIntegration } from '../api/use_cloud_defend_integration'; +import { useKibana } from '../hooks/use_kibana'; + +export const useCloudDefendIntegrationLinks = (): { + addIntegrationLink: string | undefined; + docsLink: string; +} => { + const { http } = useKibana().services; + const cloudDefendIntegration = useCloudDefendIntegration(); + + if (!cloudDefendIntegration.isSuccess) + return { + addIntegrationLink: undefined, + docsLink: 'https://www.elastic.co/guide/index.html', + }; + + const addIntegrationLink = pagePathGetters + .add_integration_to_policy({ + integration: INTEGRATION_PACKAGE_NAME, + pkgkey: pkgKeyFromPackageInfo({ + name: cloudDefendIntegration.data.item.name, + version: cloudDefendIntegration.data.item.version, + }), + }) + .join(''); + + const docsLink = pagePathGetters + .integration_details_overview({ + integration: INTEGRATION_PACKAGE_NAME, + pkgkey: pkgKeyFromPackageInfo({ + name: cloudDefendIntegration.data.item.name, + version: cloudDefendIntegration.data.item.version, + }), + }) + .join(''); + + return { + addIntegrationLink: http.basePath.prepend(addIntegrationLink), + docsLink: http.basePath.prepend(docsLink), + }; +}; diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx new file mode 100644 index 0000000000000..4c620a73dcfa9 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx @@ -0,0 +1,361 @@ +/* + * 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 { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import Chance from 'chance'; +import { + CloudDefendPage, + DEFAULT_NO_DATA_TEST_SUBJECT, + ERROR_STATE_TEST_SUBJECT, + isCommonError, + LOADING_STATE_TEST_SUBJECT, + PACKAGE_NOT_INSTALLED_TEST_SUBJECT, + SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT, +} from '.'; +import { createReactQueryResponse } from '../../test/fixtures/react_query'; +import { TestProvider } from '../../test/test_provider'; +import { coreMock } from '@kbn/core/public/mocks'; +import { render, screen } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; +import { UseQueryResult } from '@tanstack/react-query'; +import { NoDataPage } from '@kbn/kibana-react-plugin/public'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +const chance = new Chance(); + +jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); +jest.mock('../../common/navigation/use_cloud_defend_integration_links'); + +describe('', () => { + beforeEach(() => { + jest.resetAllMocks(); + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'indexed' }, + }) + ); + + (useCloudDefendIntegrationLinks as jest.Mock).mockImplementation(() => ({ + addIntegrationLink: chance.url(), + docsLink: chance.url(), + })); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + }); + + const renderCloudDefendPage = ( + props: ComponentProps = { children: null } + ) => { + const mockCore = coreMock.createStart(); + + render( + + + + ); + }; + + it('renders children if setup status is indexed', () => { + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByText(children)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading state when the subscription query is loading', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error state when the subscription query has an error', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'error', + error: new Error('error'), + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders subscription not allowed prompt if subscription is not installed', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: false, + }) + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.getByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders integrations installation prompt if integration is not installed', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'not-installed' }, + }) + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading state when the integration query is loading', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error state when the integration query has an error', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'error', + error: new Error('error'), + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading text when query isLoading', () => { + const query = createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading text when query is idle', () => { + const query = createReactQueryResponse({ + status: 'idle', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error texts when query isError', () => { + const error = chance.sentence(); + const message = chance.sentence(); + const statusCode = chance.integer(); + + const query = createReactQueryResponse({ + status: 'error', + error: { + body: { + error, + message, + statusCode, + }, + }, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + [error, message, statusCode].forEach((text) => + expect(screen.getByText(text, { exact: false })).toBeInTheDocument() + ); + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom error render', () => { + const error = chance.sentence(); + const message = chance.sentence(); + const statusCode = chance.integer(); + + const query = createReactQueryResponse({ + status: 'error', + error: { + body: { + error, + message, + statusCode, + }, + }, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + errorRender: (err) =>
    {isCommonError(err) && err.body.message}
    , + }); + + expect(screen.getByText(message)).toBeInTheDocument(); + [error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument()); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom loading render', () => { + const loading = chance.sentence(); + + const query = createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + loadingRender: () =>
    {loading}
    , + }); + + expect(screen.getByText(loading)).toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders no data prompt when query data is undefined', () => { + const query = createReactQueryResponse({ + status: 'success', + data: undefined, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(DEFAULT_NO_DATA_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom no data prompt', () => { + const pageTitle = chance.sentence(); + const solution = chance.sentence(); + const docsLink = chance.sentence(); + const noDataRenderer = () => ( + + ); + + const query = createReactQueryResponse({ + status: 'success', + data: undefined, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + noDataRenderer, + }); + + expect(screen.getByText(pageTitle)).toBeInTheDocument(); + expect(screen.getAllByText(solution, { exact: false })[0]).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx new file mode 100644 index 0000000000000..bc10db7ace74b --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx @@ -0,0 +1,294 @@ +/* + * 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 type { UseQueryResult } from '@tanstack/react-query'; +import { + EuiButton, + EuiEmptyPrompt, + EuiImage, + EuiFlexGroup, + EuiFlexItem, + EuiLink, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { NoDataPage, NoDataPageProps } from '@kbn/kibana-react-plugin/public'; +import { css } from '@emotion/react'; +import { SubscriptionNotAllowed } from '../subscription_not_allowed'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import { FullSizeCenteredPage } from '../full_size_page'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { LoadingState } from '../loading_state'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +import noDataIllustration from '../../assets/icons/logo.svg'; + +export const LOADING_STATE_TEST_SUBJECT = 'cloud_defend_page_loading'; +export const ERROR_STATE_TEST_SUBJECT = 'cloud_defend_page_error'; +export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_defend_page_package_not_installed'; +export const DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_defend_page_no_data'; +export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_defend_page_subscription_not_allowed'; + +interface CommonError { + body: { + error: string; + message: string; + statusCode: number; + }; +} + +export const isCommonError = (error: unknown): error is CommonError => { + if ( + !(error as any)?.body || + !(error as any)?.body?.error || + !(error as any)?.body?.message || + !(error as any)?.body?.statusCode + ) { + return false; + } + + return true; +}; + +export interface CloudDefendNoDataPageProps { + pageTitle: NoDataPageProps['pageTitle']; + docsLink: NoDataPageProps['docsLink']; + actionHref: NoDataPageProps['actions']['elasticAgent']['href']; + actionTitle: NoDataPageProps['actions']['elasticAgent']['title']; + actionDescription: NoDataPageProps['actions']['elasticAgent']['description']; + testId: string; +} + +export const CloudDefendNoDataPage = ({ + pageTitle, + docsLink, + actionHref, + actionTitle, + actionDescription, + testId, +}: CloudDefendNoDataPageProps) => { + return ( + :nth-child(3) { + display: block; + margin: auto; + width: 450px; + } + `} + pageTitle={pageTitle} + solution={i18n.translate( + 'xpack.cloudDefend.cloudDefendPage.packageNotInstalled.solutionNameLabel', + { + defaultMessage: 'Defend for containers (D4C)', + } + )} + docsLink={docsLink} + logo="logoSecurity" + actions={{ + elasticAgent: { + href: actionHref, + isDisabled: !actionHref, + title: actionTitle, + description: actionDescription, + }, + }} + /> + ); +}; + +const packageNotInstalledRenderer = ({ + addIntegrationLink, + docsLink, +}: { + addIntegrationLink?: string; + docsLink?: string; +}) => { + return ( + + } + title={ +

    + +

    + } + layout="horizontal" + color="plain" + body={ +

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

    + } + actions={ + + + + + + + + } + /> +
    + ); +}; + +const defaultLoadingRenderer = () => ( + + + +); + +const defaultErrorRenderer = (error: unknown) => ( + + + + + } + body={ + isCommonError(error) ? ( +

    + +

    + ) : undefined + } + /> +
    +); + +const defaultNoDataRenderer = (docsLink: string) => ( + + + +); + +const subscriptionNotAllowedRenderer = () => ( + + + +); + +interface CloudDefendPageProps { + children: React.ReactNode; + query?: UseQueryResult; + loadingRender?: () => React.ReactNode; + errorRender?: (error: TError) => React.ReactNode; + noDataRenderer?: (docsLink: string) => React.ReactNode; +} + +export const CloudDefendPage = ({ + children, + query, + loadingRender = defaultLoadingRenderer, + errorRender = defaultErrorRenderer, + noDataRenderer = defaultNoDataRenderer, +}: CloudDefendPageProps) => { + const subscriptionStatus = useSubscriptionStatus(); + const getSetupStatus = useCloudDefendSetupStatusApi(); + const { addIntegrationLink, docsLink } = useCloudDefendIntegrationLinks(); + + const render = () => { + if (subscriptionStatus.isError) { + return defaultErrorRenderer(subscriptionStatus.error); + } + + if (subscriptionStatus.isLoading) { + return defaultLoadingRenderer(); + } + + if (!subscriptionStatus.data) { + return subscriptionNotAllowedRenderer(); + } + + if (getSetupStatus.isError) { + return defaultErrorRenderer(getSetupStatus.error); + } + + if (getSetupStatus.isLoading) { + return defaultLoadingRenderer(); + } + + if (getSetupStatus.data.status === 'not-installed') { + return packageNotInstalledRenderer({ addIntegrationLink, docsLink }); + } + + if (!query) { + return children; + } + + if (query.isError) { + return errorRender(query.error); + } + + if (query.isLoading) { + return loadingRender(); + } + + if (!query.data) { + return noDataRenderer(docsLink); + } + + return children; + }; + + return <>{render()}; +}; diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx new file mode 100644 index 0000000000000..43422ff2db1e6 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx @@ -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 React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; + +export const CloudDefendPageTitle = ({ title }: { title: string }) => ( + + + +

    {title}

    +
    +
    +
    +); diff --git a/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx b/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx new file mode 100644 index 0000000000000..4e68797c8c21f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, type CommonProps } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; + +// Keep this component lean as it is part of the main app bundle +export const FullSizeCenteredPage = ({ + children, + ...rest +}: { children: React.ReactNode } & CommonProps) => ( + + {children} + +); diff --git a/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx b/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx new file mode 100644 index 0000000000000..608eabf0e30a8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx @@ -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 { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { FullSizeCenteredPage } from '../full_size_page'; + +// Keep this component lean as it is part of the main app bundle +export const LoadingState: React.FunctionComponent<{ ['data-test-subj']?: string }> = ({ + children, + ...rest +}) => { + return ( + + + + {children} + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx new file mode 100644 index 0000000000000..e473fd9f990b1 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 Chance from 'chance'; +import { render, screen } from '@testing-library/react'; +import moment from 'moment'; +import { createCloudDefendIntegrationFixture } from '../../test/fixtures/cloud_defend_integration'; +import { PoliciesTable } from '.'; +import { TestProvider } from '../../test/test_provider'; + +describe('', () => { + const chance = new Chance(); + + const tableProps = { + pageIndex: 1, + pageSize: 10, + error: undefined, + loading: false, + setQuery: jest.fn(), + }; + + it('renders integration name', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(item.package_policy.name)).toBeInTheDocument(); + }); + + it('renders agent policy name', () => { + const agentPolicy = { + id: chance.guid(), + name: chance.sentence(), + agents: chance.integer({ min: 1 }), + }; + + const policies = [createCloudDefendIntegrationFixture({ agent_policy: agentPolicy })]; + + render( + + + + ); + + expect(screen.getByText(agentPolicy.name)).toBeInTheDocument(); + }); + + it('renders number of agents', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + // TODO too loose + expect(screen.getByText(item.agent_policy.agents as number)).toBeInTheDocument(); + }); + + it('renders created by', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(item.package_policy.created_by)).toBeInTheDocument(); + }); + + it('renders created at', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(moment(item.package_policy.created_at).fromNow())).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx b/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx new file mode 100644 index 0000000000000..bb4134b083d9f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx @@ -0,0 +1,157 @@ +/* + * 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 { + EuiBasicTable, + type EuiBasicTableColumn, + type EuiBasicTableProps, + type Pagination, + type CriteriaWithPagination, + EuiLink, +} from '@elastic/eui'; +import React from 'react'; +import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { TimestampTableCell } from '../timestamp_table_cell'; +import type { CloudDefendPolicy } from '../../../common/types'; +import { useKibana } from '../../common/hooks/use_kibana'; +import * as TEST_SUBJ from '../../pages/policies/test_subjects'; + +interface PoliciesTableProps + extends Pick< + EuiBasicTableProps, + 'loading' | 'error' | 'noItemsMessage' | 'sorting' + >, + Pagination { + policies: CloudDefendPolicy[]; + setQuery(pagination: CriteriaWithPagination): void; + 'data-test-subj'?: string; +} + +const AgentPolicyButtonLink = ({ name, id: policyId }: { name: string; id: string }) => { + const { http } = useKibana().services; + const [fleetBase, path] = pagePathGetters.policy_details({ policyId }); + + return {name}; +}; + +const IntegrationButtonLink = ({ + packageName, + policyId, + packagePolicyId, +}: { + packageName: string; + packagePolicyId: string; + policyId: string; +}) => { + const editIntegrationLink = pagePathGetters + .edit_integration({ + packagePolicyId, + policyId, + }) + .join(''); + + return {packageName}; +}; + +const POLICIES_TABLE_COLUMNS: Array> = [ + { + field: 'package_policy.name', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.integrationNameColumnTitle', { + defaultMessage: 'Integration Name', + }), + render: (packageName, policy) => ( + + ), + truncateText: true, + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.INTEGRATION_NAME, + }, + { + field: 'agent_policy.name', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.agentPolicyColumnTitle', { + defaultMessage: 'Agent Policy', + }), + render: (name, policy) => , + truncateText: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.AGENT_POLICY, + }, + { + field: 'agent_policy.agents', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.numberOfAgentsColumnTitle', { + defaultMessage: 'Number of Agents', + }), + truncateText: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.NUMBER_OF_AGENTS, + }, + { + field: 'package_policy.created_by', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.createdByColumnTitle', { + defaultMessage: 'Created by', + }), + dataType: 'string', + truncateText: true, + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.CREATED_BY, + }, + { + field: 'package_policy.created_at', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.createdAtColumnTitle', { + defaultMessage: 'Created at', + }), + dataType: 'date', + truncateText: true, + render: (timestamp: CloudDefendPolicy['package_policy']['created_at']) => ( + + ), + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.CREATED_AT, + }, +]; + +export const PoliciesTable = ({ + policies, + pageIndex, + pageSize, + totalItemCount, + loading, + error, + setQuery, + noItemsMessage, + sorting, + ...rest +}: PoliciesTableProps) => { + const pagination: Pagination = { + pageIndex: Math.max(pageIndex - 1, 0), + pageSize, + totalItemCount, + }; + + const onChange = ({ page, sort }: CriteriaWithPagination) => { + setQuery({ page: { ...page, index: page.index + 1 }, sort }); + }; + + return ( + [item.agent_policy.id, item.package_policy.id].join('/')} + pagination={pagination} + onChange={onChange} + tableLayout="fixed" + loading={loading} + noItemsMessage={noItemsMessage} + error={error} + sorting={sorting} + /> + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.tsx b/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.tsx new file mode 100644 index 0000000000000..7ab4afa3fb06e --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.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 { EuiEmptyPrompt, EuiPageSection, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { useKibana } from '../../common/hooks/use_kibana'; + +export const SubscriptionNotAllowed = () => { + const { application } = useKibana().services; + return ( + + + + + } + body={ +

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

    + } + /> +
    + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx b/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx new file mode 100644 index 0000000000000..b6b6934b92cde --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx @@ -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 React from 'react'; +import moment, { type MomentInput } from 'moment'; +import { EuiToolTip, formatDate } from '@elastic/eui'; +import { useUiSetting } from '@kbn/kibana-react-plugin/public'; + +const DEFAULT_DATE_FORMAT = 'dateFormat'; + +export const TimestampTableCell = ({ timestamp }: { timestamp: MomentInput }) => { + const dateFormat = useUiSetting(DEFAULT_DATE_FORMAT); + const formatted = formatDate(timestamp, dateFormat); + + return ( + + {moment(timestamp).fromNow()} + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/index.ts b/x-pack/plugins/cloud_defend/public/index.ts index fd8099aa2ed11..b74c04111c91b 100755 --- a/x-pack/plugins/cloud_defend/public/index.ts +++ b/x-pack/plugins/cloud_defend/public/index.ts @@ -6,6 +6,14 @@ */ import { CloudDefendPlugin } from './plugin'; +export type { CloudDefendSecuritySolutionContext } from './types'; +export { + getSecuritySolutionLink, + getSecuritySolutionNavTab, +} from './common/navigation/security_solution_links'; +export { CLOUD_DEFEND_BASE_PATH } from './common/navigation/constants'; +export type { CloudDefendPageId } from './common/navigation/types'; + // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. export function plugin() { diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts b/x-pack/plugins/cloud_defend/public/pages/index.ts similarity index 66% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts rename to x-pack/plugins/cloud_defend/public/pages/index.ts index 9e4c0ef21fdea..ec35f87e70811 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts +++ b/x-pack/plugins/cloud_defend/public/pages/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { IndexPatternStatsCollector } from './index_pattern_stats_collector'; -export type { IndexPatternStats } from './types'; +export { Policies } from './policies'; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx b/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx new file mode 100644 index 0000000000000..61fcf6f0a844a --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx @@ -0,0 +1,93 @@ +/* + * 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 Chance from 'chance'; +import { render, screen } from '@testing-library/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { createCloudDefendIntegrationFixture } from '../../test/fixtures/cloud_defend_integration'; +import { createReactQueryResponse } from '../../test/fixtures/react_query'; +import { TestProvider } from '../../test/test_provider'; +import { Policies } from '.'; +import * as TEST_SUBJ from './test_subjects'; +import { useCloudDefendPolicies } from './use_cloud_defend_policies'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +jest.mock('./use_cloud_defend_policies'); +jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); +jest.mock('../../common/navigation/use_cloud_defend_integration_links'); + +const chance = new Chance(); + +describe('', () => { + beforeEach(() => { + jest.resetAllMocks(); + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'indexed' }, + }) + ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + + (useCloudDefendIntegrationLinks as jest.Mock).mockImplementation(() => ({ + addIntegrationLink: chance.url(), + docsLink: chance.url(), + })); + }); + + const renderPolicies = (queryResponse: Partial = createReactQueryResponse()) => { + (useCloudDefendPolicies as jest.Mock).mockImplementation(() => queryResponse); + + return render( + + + + ); + }; + + it('renders the page header', () => { + renderPolicies(); + + expect(screen.getByTestId(TEST_SUBJ.POLICIES_PAGE_HEADER)).toBeInTheDocument(); + }); + + it('renders the "add integration" button', () => { + renderPolicies(); + + expect(screen.getByTestId(TEST_SUBJ.ADD_INTEGRATION_TEST_SUBJ)).toBeInTheDocument(); + }); + + it('renders error state while there is an error', () => { + const error = new Error('message'); + renderPolicies(createReactQueryResponse({ status: 'error', error })); + + expect(screen.getByText(error.message)).toBeInTheDocument(); + }); + + it('renders the benchmarks table', () => { + renderPolicies( + createReactQueryResponse({ + status: 'success', + data: { total: 1, items: [createCloudDefendIntegrationFixture()] }, + }) + ); + + expect(screen.getByTestId(TEST_SUBJ.POLICIES_TABLE_DATA_TEST_SUBJ)).toBeInTheDocument(); + Object.values(TEST_SUBJ.POLICIES_TABLE_COLUMNS).forEach((testId) => + expect(screen.getAllByTestId(testId)[0]).toBeInTheDocument() + ); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx b/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx new file mode 100644 index 0000000000000..c732be5421a17 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx @@ -0,0 +1,197 @@ +/* + * 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 { + EuiButton, + EuiFieldSearch, + EuiFieldSearchProps, + EuiFlexGroup, + EuiFlexItem, + EuiPageHeader, + EuiSpacer, + EuiText, + EuiTextColor, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { i18n } from '@kbn/i18n'; +import { CloudDefendPageTitle } from '../../components/cloud_defend_page_title'; +import { CloudDefendPage } from '../../components/cloud_defend_page'; +import { PoliciesTable } from '../../components/policies_table'; +import { useCloudDefendPolicies, UseCloudDefendPoliciesProps } from './use_cloud_defend_policies'; +import { extractErrorMessage } from '../../../common/utils/helpers'; +import * as TEST_SUBJ from './test_subjects'; +import { LOCAL_STORAGE_PAGE_SIZE } from '../../common/constants'; +import { usePageSize } from '../../common/hooks/use_page_size'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +const SEARCH_DEBOUNCE_MS = 300; + +const AddIntegrationButton = () => { + const { addIntegrationLink } = useCloudDefendIntegrationLinks(); + + return ( + + + + ); +}; + +const EmptyState = ({ name }: { name: string }) => ( +
    + + { + + + + {name && ( + + )} + + + } + + + + + + + +
    +); + +const TotalIntegrationsCount = ({ + pageCount, + totalCount, +}: Record<'pageCount' | 'totalCount', number>) => ( + + + + + +); + +const SearchField = ({ + onSearch, + isLoading, +}: Required>) => { + const [localValue, setLocalValue] = useState(''); + + useDebounce(() => onSearch(localValue), SEARCH_DEBOUNCE_MS, [localValue]); + + return ( + + + + + + ); +}; + +export const Policies = () => { + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE); + const [query, setQuery] = useState({ + name: '', + page: 1, + perPage: pageSize, + sortField: 'package_policy.name', + sortOrder: 'asc', + }); + + const queryResult = useCloudDefendPolicies(query); + const totalItemCount = queryResult.data?.total || 0; + + return ( + + + } + rightSideItems={[]} + bottomBorder + /> + + setQuery((current) => ({ ...current, name }))} + /> + + + + { + setPageSize(page.size); + setQuery((current) => ({ + ...current, + page: page.index, + perPage: page.size, + sortField: + (sort?.field as UseCloudDefendPoliciesProps['sortField']) || current.sortField, + sortOrder: sort?.direction || current.sortOrder, + })); + }} + noItemsMessage={ + queryResult.isSuccess && !queryResult.data.total ? ( + + ) : undefined + } + /> + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.ts b/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.ts new file mode 100644 index 0000000000000..9709360512727 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.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. + */ + +export const POLICIES_PAGE_HEADER = 'policies-page-header'; +export const POLICIES_TABLE_DATA_TEST_SUBJ = 'cloud_defend_policies_table'; +export const ADD_INTEGRATION_TEST_SUBJ = 'cloud_defend_add_integration'; +export const POLICIES_TABLE_COLUMNS = { + INTEGRATION_NAME: 'policies-table-column-integration-name', + AGENT_POLICY: 'policies-table-column-agent-policy', + NUMBER_OF_AGENTS: 'policies-table-column-number-of-agents', + CREATED_BY: 'policies-table-column-created-by', + CREATED_AT: 'policies-table-column-created-at', +}; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts b/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts new file mode 100644 index 0000000000000..e0766026e12b8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { ListResult } from '@kbn/fleet-plugin/common'; +import { POLICIES_ROUTE_PATH } from '../../../common/constants'; +import type { PoliciesQueryParams } from '../../../common/schemas/policy'; +import { useKibana } from '../../common/hooks/use_kibana'; +import type { CloudDefendPolicy } from '../../../common/types'; + +const QUERY_KEY = 'cloud_defend_policies'; + +export interface UseCloudDefendPoliciesProps { + name: string; + page: number; + perPage: number; + sortField: PoliciesQueryParams['sort_field']; + sortOrder: PoliciesQueryParams['sort_order']; +} + +export const useCloudDefendPolicies = ({ + name, + perPage, + page, + sortField, + sortOrder, +}: UseCloudDefendPoliciesProps) => { + const { http } = useKibana().services; + const query: PoliciesQueryParams = { + policy_name: name, + per_page: perPage, + page, + sort_field: sortField, + sort_order: sortOrder, + }; + + return useQuery( + [QUERY_KEY, query], + () => http.get>(POLICIES_ROUTE_PATH, { query }), + { keepPreviousData: true } + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/plugin.ts b/x-pack/plugins/cloud_defend/public/plugin.ts deleted file mode 100755 index 5bbb1215e2270..0000000000000 --- a/x-pack/plugins/cloud_defend/public/plugin.ts +++ /dev/null @@ -1,44 +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 { lazy } from 'react'; -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { - CloudDefendPluginSetup, - CloudDefendPluginStart, - CloudDefendPluginStartDeps, -} from './types'; -import { INTEGRATION_PACKAGE_NAME } from '../common/constants'; - -const LazyEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit')); -const LazyCreatePolicy = lazy( - () => import('./components/fleet_extensions/policy_extension_create') -); - -export class CloudDefendPlugin implements Plugin { - public setup(core: CoreSetup): CloudDefendPluginSetup { - // Return methods that should be available to other plugins - return {}; - } - - public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { - plugins.fleet.registerExtension({ - package: INTEGRATION_PACKAGE_NAME, - view: 'package-policy-create', - Component: LazyCreatePolicy, - }); - - plugins.fleet.registerExtension({ - package: INTEGRATION_PACKAGE_NAME, - view: 'package-policy-edit', - Component: LazyEditPolicy, - }); - - return {}; - } - - public stop() {} -} diff --git a/x-pack/plugins/cloud_defend/public/plugin.tsx b/x-pack/plugins/cloud_defend/public/plugin.tsx new file mode 100755 index 0000000000000..b9272993e6b55 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/plugin.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import React, { lazy, Suspense } from 'react'; +import type { CloudDefendRouterProps } from './application/router'; +import { + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, + CloudDefendPluginSetupDeps, +} from './types'; +import { INTEGRATION_PACKAGE_NAME } from '../common/constants'; +import { LoadingState } from './components/loading_state'; +import { SetupContext } from './application/setup_context'; + +const LazyEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit')); +const LazyCreatePolicy = lazy( + () => import('./components/fleet_extensions/policy_extension_create') +); + +const RouterLazy = lazy(() => import('./application/router')); +const Router = (props: CloudDefendRouterProps) => ( + }> + + +); + +export class CloudDefendPlugin + implements + Plugin< + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginSetupDeps, + CloudDefendPluginStartDeps + > +{ + private isCloudEnabled?: boolean; + + public setup( + core: CoreSetup, + plugins: CloudDefendPluginSetupDeps + ): CloudDefendPluginSetup { + this.isCloudEnabled = plugins.cloud.isCloudEnabled; + + // Return methods that should be available to other plugins + return {}; + } + + public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { + plugins.fleet.registerExtension({ + package: INTEGRATION_PACKAGE_NAME, + view: 'package-policy-create', + Component: LazyCreatePolicy, + }); + + plugins.fleet.registerExtension({ + package: INTEGRATION_PACKAGE_NAME, + view: 'package-policy-edit', + Component: LazyEditPolicy, + }); + + const CloudDefendRouter = (props: CloudDefendRouterProps) => ( + + +
    + + + +
    +
    +
    + ); + + return { + getCloudDefendRouter: () => CloudDefendRouter, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.ts new file mode 100644 index 0000000000000..830977947673c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.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. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import Chance from 'chance'; +import type { CloudDefendPolicy } from '../../../common/types'; + +type CreateCloudDefendIntegrationFixtureInput = { + chance?: Chance.Chance; +} & Partial; + +export const createCloudDefendIntegrationFixture = ({ + chance = new Chance(), + package_policy = { + revision: chance?.integer(), + enabled: true, + id: chance.guid(), + name: chance.string(), + policy_id: chance.guid(), + namespace: chance.string(), + updated_at: chance.date().toISOString(), + updated_by: chance.word(), + created_at: chance.date().toISOString(), + created_by: chance.word(), + inputs: [ + { + type: 'cloud_defend/control', + policy_template: 'cloud_defend', + enabled: true, + streams: [ + { + id: chance?.guid(), + enabled: true, + data_stream: { + type: 'logs', + dataset: 'cloud_defend.alerts', + }, + }, + ], + }, + ], + package: { + name: chance.string(), + title: chance.string(), + version: chance.string(), + }, + }, + agent_policy = { + id: chance.guid(), + name: chance.sentence(), + agents: chance.integer({ min: 0 }), + }, +}: CreateCloudDefendIntegrationFixtureInput = {}): CloudDefendPolicy => ({ + package_policy, + agent_policy, +}); diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.ts new file mode 100644 index 0000000000000..74fdbce35f95b --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.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 Chance from 'chance'; +import type { CloudDefendPageNavigationItem } from '../../common/navigation/types'; + +type CreateNavigationItemFixtureInput = { + chance?: Chance.Chance; +} & Partial; +export const createPageNavigationItemFixture = ({ + chance = new Chance(), + name = chance.word(), + path = `/${chance.word()}`, + disabled = undefined, + id = 'cloud_defend-policies', +}: CreateNavigationItemFixtureInput = {}): CloudDefendPageNavigationItem => ({ + name, + path, + disabled, + id, +}); diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.ts new file mode 100644 index 0000000000000..9605515618882 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.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. + */ + +import type { UseQueryResult } from '@tanstack/react-query'; + +interface CreateReactQueryResponseInput { + status?: UseQueryResult['status'] | 'idle'; + data?: TData; + error?: TError; +} + +// TODO: Consider alternatives to using `Partial` over `UseQueryResult` for the return type: +// 1. Fully mock `UseQueryResult` +// 2. Mock the network layer instead of `useQuery` - see: https://tkdodo.eu/blog/testing-react-query +export const createReactQueryResponse = ({ + status = 'loading', + error = undefined, + data = undefined, +}: CreateReactQueryResponseInput = {}): Partial> => { + if (status === 'success') { + return { status, data, isSuccess: true, isLoading: false, isError: false }; + } + + if (status === 'error') { + return { status, error, isSuccess: false, isLoading: false, isError: true }; + } + + if (status === 'loading') { + return { status, data: undefined, isSuccess: false, isLoading: true, isError: false }; + } + + if (status === 'idle') { + return { + status: 'loading', + data: undefined, + isSuccess: false, + isLoading: true, + isError: false, + fetchStatus: 'idle', + }; + } + + return { status }; +}; diff --git a/x-pack/plugins/cloud_defend/public/test/mocks.ts b/x-pack/plugins/cloud_defend/public/test/mocks.ts index 7dcf8d99d9116..4921d3f6d0f98 100644 --- a/x-pack/plugins/cloud_defend/public/test/mocks.ts +++ b/x-pack/plugins/cloud_defend/public/test/mocks.ts @@ -71,8 +71,8 @@ export const getCloudDefendNewPolicyMock = (yaml = MOCK_YAML_CONFIGURATION): New ], package: { name: 'cloud_defend', - title: 'Kubernetes Security Posture Management', - version: '0.0.21', + title: 'Container drift prevention', + version: '1.0.0', }, }); @@ -114,7 +114,7 @@ export const getCloudDefendPolicyMock = (yaml = MOCK_YAML_CONFIGURATION): Packag ], package: { name: 'cloud_defend', - title: 'Kubernetes Security Posture Management', - version: '0.0.21', + title: 'Container drift prevention', + version: '1.0.0', }, }); diff --git a/x-pack/plugins/cloud_defend/public/test/test_provider.tsx b/x-pack/plugins/cloud_defend/public/test/test_provider.tsx index 472f0ea04ecd1..c5deff816241c 100755 --- a/x-pack/plugins/cloud_defend/public/test/test_provider.tsx +++ b/x-pack/plugins/cloud_defend/public/test/test_provider.tsx @@ -37,13 +37,13 @@ Object.defineProperty(window, 'matchMedia', { })), }); -interface CspAppDeps { +interface CloudDefendAppDeps { core: CoreStart; deps: CloudDefendPluginStartDeps; params: AppMountParameters; } -export const TestProvider: React.FC> = ({ +export const TestProvider: React.FC> = ({ core = coreMock.createStart(), deps = { data: dataPluginMock.createStartContract(), diff --git a/x-pack/plugins/cloud_defend/public/types.ts b/x-pack/plugins/cloud_defend/public/types.ts index 801f5ee67891e..3b242b9fc2add 100755 --- a/x-pack/plugins/cloud_defend/public/types.ts +++ b/x-pack/plugins/cloud_defend/public/types.ts @@ -4,22 +4,53 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { FleetSetup, FleetStart } from '@kbn/fleet-plugin/public'; import { NewPackagePolicy } from '@kbn/fleet-plugin/public'; +import type { ComponentType, ReactNode } from 'react'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; +import type { CloudDefendRouterProps } from './application/router'; +import type { CloudDefendPageId } from './common/navigation/types'; + +/** + * cloud_defend plugin types + */ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CloudDefendPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CloudDefendPluginStart {} +export interface CloudDefendPluginStart { + /** Gets the cloud defend router component for embedding in the security solution. */ + getCloudDefendRouter(): ComponentType; +} export interface CloudDefendPluginSetupDeps { fleet: FleetSetup; + cloud: CloudSetup; + usageCollection?: UsageCollectionSetup; } export interface CloudDefendPluginStartDeps { fleet: FleetStart; + licensing: LicensingPluginStart; + usageCollection?: UsageCollectionStart; } +export interface CloudDefendSecuritySolutionContext { + /** Gets the `FiltersGlobal` component for embedding a filter bar in the security solution application. */ + getFiltersGlobalComponent: () => ComponentType<{ children: ReactNode }>; + /** Gets the `SpyRoute` component for navigation highlighting and breadcrumbs. */ + getSpyRouteComponent: () => ComponentType<{ + pageName: CloudDefendPageId; + state?: Record; + }>; +} + +/** + * cloud_defend/control types + */ export enum ControlResponseAction { alert = 'alert', block = 'block', diff --git a/x-pack/plugins/cloud_defend/server/index.ts b/x-pack/plugins/cloud_defend/server/index.ts new file mode 100644 index 0000000000000..2cb2e1c2b55e5 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/server'; +import { CloudDefendPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudDefendPlugin(initializerContext); +} + +export type { CloudDefendPluginSetup, CloudDefendPluginStart } from './types'; diff --git a/x-pack/plugins/cloud_defend/server/lib/check_index_status.ts b/x-pack/plugins/cloud_defend/server/lib/check_index_status.ts new file mode 100644 index 0000000000000..984c68a76a6b6 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/lib/check_index_status.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 { ElasticsearchClient, type Logger } from '@kbn/core/server'; +import { IndexStatus } from '../../common/types'; + +export const checkIndexStatus = async ( + esClient: ElasticsearchClient, + index: string, + logger: Logger +): Promise => { + try { + const queryResult = await esClient.search({ + index, + query: { + match_all: {}, + }, + size: 1, + }); + + return queryResult.hits.hits.length ? 'not-empty' : 'empty'; + } catch (e) { + logger.debug(e); + if (e?.meta?.body?.error?.type === 'security_exception') { + return 'unprivileged'; + } + + // Assuming index doesn't exist + return 'empty'; + } +}; diff --git a/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts new file mode 100644 index 0000000000000..ae9866b4f03ca --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { map, uniq } from 'lodash'; +import type { SavedObjectsClientContract, Logger } from '@kbn/core/server'; +import type { + AgentPolicyServiceInterface, + AgentService, + PackagePolicyClient, +} from '@kbn/fleet-plugin/server'; +import type { + AgentPolicy, + GetAgentStatusResponse, + ListResult, + PackagePolicy, +} from '@kbn/fleet-plugin/common'; +import { errors } from '@elastic/elasticsearch'; +import { INPUT_CONTROL, CLOUD_DEFEND_FLEET_PACKAGE_KUERY } from '../../common/constants'; +import { POLICIES_PACKAGE_POLICY_PREFIX, PoliciesQueryParams } from '../../common/schemas/policy'; + +export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +const isFleetMissingAgentHttpError = (error: unknown) => + error instanceof errors.ResponseError && error.statusCode === 404; + +const isPolicyTemplate = (input: any) => input === INPUT_CONTROL; + +const getPackageNameQuery = (packageName: string, benchmarkFilter?: string): string => { + const integrationNameQuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`; + const kquery = benchmarkFilter + ? `${integrationNameQuery} AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *${benchmarkFilter}*` + : integrationNameQuery; + + return kquery; +}; + +export type AgentStatusByAgentPolicyMap = Record; + +export const getAgentStatusesByAgentPolicies = async ( + agentService: AgentService, + agentPolicies: AgentPolicy[] | undefined, + logger: Logger +): Promise => { + if (!agentPolicies?.length) return {}; + + const internalAgentService = agentService.asInternalUser; + const result: AgentStatusByAgentPolicyMap = {}; + + try { + for (const agentPolicy of agentPolicies) { + result[agentPolicy.id] = await internalAgentService.getAgentStatusForAgentPolicy( + agentPolicy.id + ); + } + } catch (error) { + if (isFleetMissingAgentHttpError(error)) { + logger.debug('failed to get agent status for agent policy'); + } else { + throw error; + } + } + + return result; +}; + +export const getCloudDefendAgentPolicies = async ( + soClient: SavedObjectsClientContract, + packagePolicies: PackagePolicy[], + agentPolicyService: AgentPolicyServiceInterface +): Promise => + agentPolicyService.getByIds(soClient, uniq(map(packagePolicies, 'policy_id')), { + withPackagePolicies: true, + ignoreMissing: true, + }); + +export const getCloudDefendPackagePolicies = ( + soClient: SavedObjectsClientContract, + packagePolicyService: PackagePolicyClient, + packageName: string, + queryParams: Partial +): Promise> => { + const sortField = queryParams.sort_field?.replaceAll(POLICIES_PACKAGE_POLICY_PREFIX, ''); + + return packagePolicyService.list(soClient, { + kuery: getPackageNameQuery(packageName, queryParams.policy_name), + page: queryParams.page, + perPage: queryParams.per_page, + sortField, + sortOrder: queryParams.sort_order, + }); +}; + +export const getInstalledPolicyTemplates = async ( + packagePolicyClient: PackagePolicyClient, + soClient: SavedObjectsClientContract +) => { + try { + // getting all installed cloud_defend package policies + const queryResult = await packagePolicyClient.list(soClient, { + kuery: CLOUD_DEFEND_FLEET_PACKAGE_KUERY, + perPage: 1000, + }); + + // getting installed policy templates + const enabledPolicyTemplates = queryResult.items + .map((policy) => { + return policy.inputs.find((input) => input.enabled)?.policy_template; + }) + .filter(isPolicyTemplate); + + // removing duplicates + return [...new Set(enabledPolicyTemplates)]; + } catch (e) { + return []; + } +}; diff --git a/x-pack/plugins/cloud_defend/server/mocks.ts b/x-pack/plugins/cloud_defend/server/mocks.ts new file mode 100644 index 0000000000000..a3a1fb895e3c8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/mocks.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import { + createFleetRequestHandlerContextMock, + createMockAgentService, + createMockAgentPolicyService, + createPackagePolicyServiceMock, + createMockPackageService, +} from '@kbn/fleet-plugin/server/mocks'; +import { mockAuthenticatedUser } from '@kbn/security-plugin/common/model/authenticated_user.mock'; + +export const createCloudDefendRequestHandlerContextMock = () => { + const coreMockRequestContext = coreMock.createRequestHandlerContext(); + + return { + core: coreMockRequestContext, + fleet: createFleetRequestHandlerContextMock(), + cloudDefend: { + user: mockAuthenticatedUser(), + logger: loggingSystemMock.createLogger(), + esClient: coreMockRequestContext.elasticsearch.client, + soClient: coreMockRequestContext.savedObjects.client, + agentPolicyService: createMockAgentPolicyService(), + agentService: createMockAgentService(), + packagePolicyService: createPackagePolicyServiceMock(), + packageService: createMockPackageService(), + }, + }; +}; diff --git a/x-pack/plugins/cloud_defend/server/plugin.ts b/x-pack/plugins/cloud_defend/server/plugin.ts new file mode 100644 index 0000000000000..05926e82cf796 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/plugin.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; +import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; +import { + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, + CloudDefendPluginSetupDeps, +} from './types'; +import { setupRoutes } from './routes/setup_routes'; +import { isCloudDefendPackage } from '../common/utils/helpers'; +import { isSubscriptionAllowed } from '../common/utils/subscription'; + +export class CloudDefendPlugin implements Plugin { + private readonly logger: Logger; + private isCloudEnabled?: boolean; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup( + core: CoreSetup, + plugins: CloudDefendPluginSetupDeps + ) { + this.logger.debug('cloudDefend: Setup'); + + setupRoutes({ + core, + logger: this.logger, + }); + + this.isCloudEnabled = plugins.cloud.isCloudEnabled; + + return {}; + } + + public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { + this.logger.debug('cloudDefend: Started'); + + plugins.fleet.fleetSetupCompleted().then(async () => { + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async (packagePolicy: NewPackagePolicy): Promise => { + const license = await plugins.licensing.refresh(); + if (isCloudDefendPackage(packagePolicy.package?.name)) { + if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { + throw new Error( + 'To use this feature you must upgrade your subscription or start a trial' + ); + } + } + + return packagePolicy; + } + ); + }); + + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts b/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts new file mode 100644 index 0000000000000..51dbabe1d936f --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts @@ -0,0 +1,261 @@ +/* + * 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 { httpServerMock, httpServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { + policiesQueryParamsSchema, + DEFAULT_POLICIES_PER_PAGE, +} from '../../../common/schemas/policy'; +import { + PACKAGE_POLICY_SAVED_OBJECT_TYPE, + getCloudDefendPackagePolicies, + getCloudDefendAgentPolicies, +} from '../../lib/fleet_util'; +import { defineGetPoliciesRoute } from './policies'; + +import { SavedObjectsClientContract } from '@kbn/core/server'; +import { + createMockAgentPolicyService, + createPackagePolicyServiceMock, +} from '@kbn/fleet-plugin/server/mocks'; +import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; +import { createCloudDefendRequestHandlerContextMock } from '../../mocks'; + +describe('policies API', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('validate the API route path', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [config] = router.get.mock.calls[0]; + + expect(config.path).toEqual('/internal/cloud_defend/policies'); + }); + + it('should accept to a user with fleet.all privilege', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockContext = createCloudDefendRequestHandlerContextMock(); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledTimes(0); + }); + + it('should reject to a user without fleet.all privilege', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockContext = createCloudDefendRequestHandlerContextMock(); + mockContext.fleet.authz.fleet.all = false; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledTimes(1); + }); + + describe('test input schema', () => { + it('expect to find default values', async () => { + const validatedQuery = policiesQueryParamsSchema.validate({}); + + expect(validatedQuery).toMatchObject({ + page: 1, + per_page: DEFAULT_POLICIES_PER_PAGE, + }); + }); + + it('expect to find policy_name', async () => { + const validatedQuery = policiesQueryParamsSchema.validate({ + policy_name: 'my_cis_policy', + }); + + expect(validatedQuery).toMatchObject({ + page: 1, + per_page: DEFAULT_POLICIES_PER_PAGE, + policy_name: 'my_cis_policy', + }); + }); + + it('should throw when page field is not a positive integer', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ page: -2 }); + }).toThrow(); + }); + + it('should throw when per_page field is not a positive integer', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ per_page: -2 }); + }).toThrow(); + }); + }); + + it('should throw when sort_field is not string', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: true }); + }).toThrow(); + }); + + it('should not throw when sort_field is a string', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: 'package_policy.name' }); + }).not.toThrow(); + }); + + it('should throw when sort_order is not `asc` or `desc`', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'Other Direction' }); + }).toThrow(); + }); + + it('should not throw when `asc` is input for sort_order field', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'asc' }); + }).not.toThrow(); + }); + + it('should not throw when `desc` is input for sort_order field', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'desc' }); + }).not.toThrow(); + }); + + it('should not throw when fields is a known string literal', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: 'package_policy.name' }); + }).not.toThrow(); + }); + + describe('test policies utils', () => { + let mockSoClient: jest.Mocked; + + beforeEach(() => { + mockSoClient = savedObjectsClientMock.create(); + }); + + describe('test getPackagePolicies', () => { + it('should format request by package name', async () => { + const mockPackagePolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockPackagePolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_order: 'desc', + }); + + expect(mockPackagePolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + }) + ); + }); + + it('should build sort request by `sort_field` and default `sort_order`', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_field: 'package_policy.name', + sort_order: 'desc', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + sortField: 'name', + sortOrder: 'desc', + }) + ); + }); + + it('should build sort request by `sort_field` and asc `sort_order`', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_field: 'package_policy.name', + sort_order: 'asc', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + sortField: 'name', + sortOrder: 'asc', + }) + ); + }); + }); + + it('should format request by policy_name', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_order: 'desc', + policy_name: 'cloud_defend_1', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *cloud_defend_1*`, + page: 1, + perPage: 100, + }) + ); + }); + + describe('test getAgentPolicies', () => { + it('should return one agent policy id when there is duplication', async () => { + const agentPolicyService = createMockAgentPolicyService(); + const packagePolicies = [createPackagePolicyMock(), createPackagePolicyMock()]; + + await getCloudDefendAgentPolicies(mockSoClient, packagePolicies, agentPolicyService); + + expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(1); + }); + + it('should return full policy ids list when there is no id duplication', async () => { + const agentPolicyService = createMockAgentPolicyService(); + + const packagePolicy1 = createPackagePolicyMock(); + const packagePolicy2 = createPackagePolicyMock(); + packagePolicy2.policy_id = 'AnotherId'; + const packagePolicies = [packagePolicy1, packagePolicy2]; + + await getCloudDefendAgentPolicies(mockSoClient, packagePolicies, agentPolicyService); + + expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(2); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts b/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts new file mode 100644 index 0000000000000..a0dd3827ff618 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts @@ -0,0 +1,115 @@ +/* + * 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 { transformError } from '@kbn/securitysolution-es-utils'; +import type { AgentPolicy, PackagePolicy } from '@kbn/fleet-plugin/common'; +import { POLICIES_ROUTE_PATH, INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { policiesQueryParamsSchema } from '../../../common/schemas/policy'; +import type { CloudDefendPolicy } from '../../../common/types'; +import { isNonNullable } from '../../../common/utils/helpers'; +import { CloudDefendRouter } from '../../types'; +import { + getAgentStatusesByAgentPolicies, + type AgentStatusByAgentPolicyMap, + getCloudDefendAgentPolicies, + getCloudDefendPackagePolicies, +} from '../../lib/fleet_util'; + +export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +const createPolicies = ( + agentPolicies: AgentPolicy[], + agentStatusByAgentPolicyId: AgentStatusByAgentPolicyMap, + cloudDefendPackagePolicies: PackagePolicy[] +): Promise => { + const cloudDefendPackagePoliciesMap = new Map( + cloudDefendPackagePolicies.map((packagePolicy) => [packagePolicy.id, packagePolicy]) + ); + + return Promise.all( + agentPolicies.flatMap((agentPolicy) => { + const cloudDefendPackagesOnAgent = + agentPolicy.package_policies + ?.map(({ id: pckPolicyId }) => { + return cloudDefendPackagePoliciesMap.get(pckPolicyId); + }) + .filter(isNonNullable) ?? []; + + const policies = cloudDefendPackagesOnAgent.map(async (cloudDefendPackage) => { + const agentPolicyStatus = { + id: agentPolicy.id, + name: agentPolicy.name, + agents: agentStatusByAgentPolicyId[agentPolicy.id]?.total, + }; + return { + package_policy: cloudDefendPackage, + agent_policy: agentPolicyStatus, + }; + }); + + return policies; + }) + ); +}; + +export const defineGetPoliciesRoute = (router: CloudDefendRouter): void => + router.get( + { + path: POLICIES_ROUTE_PATH, + validate: { query: policiesQueryParamsSchema }, + options: { + tags: ['access:cloud-defend-read'], + }, + }, + async (context, request, response) => { + if (!(await context.fleet).authz.fleet.all) { + return response.forbidden(); + } + + const cloudDefendContext = await context.cloudDefend; + + try { + const cloudDefendPackagePolicies = await getCloudDefendPackagePolicies( + cloudDefendContext.soClient, + cloudDefendContext.packagePolicyService, + INTEGRATION_PACKAGE_NAME, + request.query + ); + + const agentPolicies = await getCloudDefendAgentPolicies( + cloudDefendContext.soClient, + cloudDefendPackagePolicies.items, + cloudDefendContext.agentPolicyService + ); + + const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies( + cloudDefendContext.agentService, + agentPolicies, + cloudDefendContext.logger + ); + + const policies = await createPolicies( + agentPolicies, + agentStatusesByAgentPolicyId, + cloudDefendPackagePolicies.items + ); + + return response.ok({ + body: { + ...cloudDefendPackagePolicies, + items: policies, + }, + }); + } catch (err) { + const error = transformError(err); + cloudDefendContext.logger.error(`Failed to fetch policies ${err}`); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ); diff --git a/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts b/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts new file mode 100644 index 0000000000000..1a9ef57ba83c7 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/setup_routes.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 type { CoreSetup, Logger } from '@kbn/core/server'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import type { + CloudDefendRequestHandlerContext, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, +} from '../types'; +import { PLUGIN_ID } from '../../common/constants'; +import { defineGetPoliciesRoute } from './policies/policies'; +import { defineGetCloudDefendStatusRoute } from './status/status'; + +/** + * 1. Registers routes + * 2. Registers routes handler context + */ +export function setupRoutes({ + core, + logger, +}: { + core: CoreSetup; + logger: Logger; +}) { + const router = core.http.createRouter(); + defineGetPoliciesRoute(router); + defineGetCloudDefendStatusRoute(router); + + core.http.registerRouteHandlerContext( + PLUGIN_ID, + async (context, request) => { + const [, { security, fleet }] = await core.getStartServices(); + const coreContext = await context.core; + await fleet.fleetSetupCompleted(); + + let user: AuthenticatedUser | null = null; + + return { + get user() { + // We want to call getCurrentUser only when needed and only once + if (!user) { + user = security.authc.getCurrentUser(request); + } + return user; + }, + logger, + esClient: coreContext.elasticsearch.client, + soClient: coreContext.savedObjects.client, + agentPolicyService: fleet.agentPolicyService, + agentService: fleet.agentService, + packagePolicyService: fleet.packagePolicyService, + packageService: fleet.packageService, + }; + } + ); +} diff --git a/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts b/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts new file mode 100644 index 0000000000000..124d8f4738cb2 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts @@ -0,0 +1,493 @@ +/* + * 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 { defineGetCloudDefendStatusRoute, INDEX_TIMEOUT_IN_MINUTES } from './status'; +import { httpServerMock, httpServiceMock } from '@kbn/core/server/mocks'; +import type { ESSearchResponse } from '@kbn/es-types'; +import { + AgentClient, + AgentPolicyServiceInterface, + AgentService, + PackageClient, + PackagePolicyClient, + PackageService, +} from '@kbn/fleet-plugin/server'; +import { + AgentPolicy, + GetAgentStatusResponse, + Installation, + RegistryPackage, +} from '@kbn/fleet-plugin/common'; +import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; +import { createCloudDefendRequestHandlerContextMock } from '../../mocks'; +import { errors } from '@elastic/elasticsearch'; + +const mockCloudDefendPackageInfo: Installation = { + verification_status: 'verified', + installed_kibana: [], + installed_kibana_space_id: 'default', + installed_es: [], + package_assets: [], + es_index_patterns: { alerts: 'logs-cloud_defend.alerts-*' }, + name: 'cloud_defend', + version: '1.0.0', + install_version: '1.0.0', + install_status: 'installed', + install_started_at: '2022-06-16T15:24:58.281Z', + install_source: 'registry', +}; + +const mockLatestCloudDefendPackageInfo: RegistryPackage = { + format_version: 'mock', + name: 'cloud_defend', + title: 'Defend for containers (D4C)', + version: '1.0.0', + release: 'experimental', + description: 'Container drift prevention', + type: 'integration', + download: '/epr/cloud_defend/cloud_defend-1.0.0.zip', + path: '/package/cloud_defend/1.0.0', + policy_templates: [], + owner: { github: 'elastic/sec-cloudnative-integrations' }, + categories: ['containers', 'kubernetes'], +}; + +describe('CloudDefendSetupStatus route', () => { + const router = httpServiceMock.createRouter(); + let mockContext: ReturnType; + let mockPackagePolicyService: jest.Mocked; + let mockAgentPolicyService: jest.Mocked; + let mockAgentService: jest.Mocked; + let mockAgentClient: jest.Mocked; + let mockPackageService: PackageService; + let mockPackageClient: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + mockContext = createCloudDefendRequestHandlerContextMock(); + mockPackagePolicyService = mockContext.cloudDefend.packagePolicyService; + mockAgentPolicyService = mockContext.cloudDefend.agentPolicyService; + mockAgentService = mockContext.cloudDefend.agentService; + mockPackageService = mockContext.cloudDefend.packageService; + + mockAgentClient = mockAgentService.asInternalUser as jest.Mocked; + mockPackageClient = mockPackageService.asInternalUser as jest.Mocked; + }); + + it('validate the API route path', async () => { + defineGetCloudDefendStatusRoute(router); + const [config, _] = router.get.mock.calls[0]; + + expect(config.path).toEqual('/internal/cloud_defend/status'); + }); + + const indices = [ + { + index: 'logs-cloud_defend.alerts-default*', + expected_status: 'not-installed', + }, + ]; + + indices.forEach((idxTestCase) => { + it( + 'Verify the API result when there are no permissions to index: ' + idxTestCase.index, + async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseImplementation( + (req) => { + if (req?.index === idxTestCase.index) { + throw new errors.ResponseError({ + body: { + error: { + type: 'security_exception', + }, + }, + statusCode: 503, + headers: {}, + warnings: [], + meta: {} as any, + }); + } + + return { + hits: { + hits: [{}], + }, + } as any; + } + ); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: idxTestCase.expected_status, + }); + } + ); + }); + + it('Verify the API result when there are alerts and no installed policies', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + await expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 0, + healthyAgents: 0, + installedPackageVersion: undefined, + }); + }); + + it('Verify the API result when there are alerts, installed policies, no running agents', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 3, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + await expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 3, + healthyAgents: 0, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are alerts, installed policies, running agents', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 3, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 3, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts and no installed policies', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + + // Act + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'not-installed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 0, + healthyAgents: 0, + }); + }); + + it('Verify the API result when there are no alerts, installed agent but no deployed agent', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 0, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'not-deployed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 0, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts, installed agent, deployed agent, before index timeout', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + const currentTime = new Date(); + mockCloudDefendPackageInfo.install_started_at = new Date( + currentTime.setMinutes(currentTime.getMinutes() - INDEX_TIMEOUT_IN_MINUTES + 1) + ).toUTCString(); + + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'indexing', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts, installed agent, deployed agent, after index timeout', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + const currentTime = new Date(); + mockCloudDefendPackageInfo.install_started_at = new Date( + currentTime.setMinutes(currentTime.getMinutes() - INDEX_TIMEOUT_IN_MINUTES - 1) + ).toUTCString(); + + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'index-timeout', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/server/routes/status/status.ts b/x-pack/plugins/cloud_defend/server/routes/status/status.ts new file mode 100644 index 0000000000000..0283843254978 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/status/status.ts @@ -0,0 +1,198 @@ +/* + * 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 { transformError } from '@kbn/securitysolution-es-utils'; +import type { SavedObjectsClientContract, Logger } from '@kbn/core/server'; +import type { AgentPolicyServiceInterface, AgentService } from '@kbn/fleet-plugin/server'; +import moment from 'moment'; +import { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { + ALERTS_INDEX_PATTERN, + INTEGRATION_PACKAGE_NAME, + STATUS_ROUTE_PATH, +} from '../../../common/constants'; +import type { CloudDefendApiRequestHandlerContext, CloudDefendRouter } from '../../types'; +import type { + CloudDefendSetupStatus, + CloudDefendStatusCode, + IndexStatus, +} from '../../../common/types'; +import { + getAgentStatusesByAgentPolicies, + getCloudDefendAgentPolicies, + getCloudDefendPackagePolicies, + getInstalledPolicyTemplates, +} from '../../lib/fleet_util'; +import { checkIndexStatus } from '../../lib/check_index_status'; + +export const INDEX_TIMEOUT_IN_MINUTES = 10; + +const calculateDiffFromNowInMinutes = (date: string | number): number => + moment().diff(moment(date), 'minutes'); + +const getHealthyAgents = async ( + soClient: SavedObjectsClientContract, + installedCloudDefendPackagePolicies: PackagePolicy[], + agentPolicyService: AgentPolicyServiceInterface, + agentService: AgentService, + logger: Logger +): Promise => { + // Get agent policies of package policies (from installed package policies) + const agentPolicies = await getCloudDefendAgentPolicies( + soClient, + installedCloudDefendPackagePolicies, + agentPolicyService + ); + + // Get agents statuses of the following agent policies + const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies( + agentService, + agentPolicies, + logger + ); + + return Object.values(agentStatusesByAgentPolicyId).reduce( + (sum, status) => sum + status.online + status.updating, + 0 + ); +}; + +const calculateCloudDefendStatusCode = ( + indicesStatus: { + alerts: IndexStatus; + }, + installedCloudDefendPackagePolicies: number, + healthyAgents: number, + timeSinceInstallationInMinutes: number +): CloudDefendStatusCode => { + // We check privileges only for the relevant indices for our pages to appear + if (indicesStatus.alerts === 'unprivileged') return 'unprivileged'; + if (indicesStatus.alerts === 'not-empty') return 'indexed'; + if (installedCloudDefendPackagePolicies === 0) return 'not-installed'; + if (healthyAgents === 0) return 'not-deployed'; + if (timeSinceInstallationInMinutes <= INDEX_TIMEOUT_IN_MINUTES) return 'indexing'; + if (timeSinceInstallationInMinutes > INDEX_TIMEOUT_IN_MINUTES) return 'index-timeout'; + + throw new Error('Could not determine cloud defend status'); +}; + +const assertResponse = ( + resp: CloudDefendSetupStatus, + logger: CloudDefendApiRequestHandlerContext['logger'] +) => { + if ( + resp.status === 'unprivileged' && + !resp.indicesDetails.some((idxDetails) => idxDetails.status === 'unprivileged') + ) { + logger.warn('Returned status in `unprivileged` but response is missing the unprivileged index'); + } +}; + +const getCloudDefendStatus = async ({ + logger, + esClient, + soClient, + packageService, + packagePolicyService, + agentPolicyService, + agentService, +}: CloudDefendApiRequestHandlerContext): Promise => { + const [ + alertsIndexStatus, + installation, + latestCloudDefendPackage, + installedPackagePolicies, + installedPolicyTemplates, + ] = await Promise.all([ + checkIndexStatus(esClient.asCurrentUser, ALERTS_INDEX_PATTERN, logger), + packageService.asInternalUser.getInstallation(INTEGRATION_PACKAGE_NAME), + packageService.asInternalUser.fetchFindLatestPackage(INTEGRATION_PACKAGE_NAME), + getCloudDefendPackagePolicies(soClient, packagePolicyService, INTEGRATION_PACKAGE_NAME, { + per_page: 10000, + }), + getInstalledPolicyTemplates(packagePolicyService, soClient), + ]); + + const healthyAgents = await getHealthyAgents( + soClient, + installedPackagePolicies.items, + agentPolicyService, + agentService, + logger + ); + + const installedPackagePoliciesTotal = installedPackagePolicies.total; + const latestCloudDefendPackageVersion = latestCloudDefendPackage.version; + + const MIN_DATE = 0; + const indicesDetails = [ + { + index: ALERTS_INDEX_PATTERN, + status: alertsIndexStatus, + }, + ]; + + const status = calculateCloudDefendStatusCode( + { + alerts: alertsIndexStatus, + }, + installedPackagePoliciesTotal, + healthyAgents, + calculateDiffFromNowInMinutes(installation?.install_started_at || MIN_DATE) + ); + + if (status === 'not-installed') + return { + status, + indicesDetails, + latestPackageVersion: latestCloudDefendPackageVersion, + healthyAgents, + installedPackagePolicies: installedPackagePoliciesTotal, + }; + + const response = { + status, + indicesDetails, + latestPackageVersion: latestCloudDefendPackageVersion, + healthyAgents, + installedPolicyTemplates, + installedPackagePolicies: installedPackagePoliciesTotal, + installedPackageVersion: installation?.install_version, + }; + + assertResponse(response, logger); + return response; +}; + +export const defineGetCloudDefendStatusRoute = (router: CloudDefendRouter): void => + router.get( + { + path: STATUS_ROUTE_PATH, + validate: {}, + options: { + tags: ['access:cloud-defend-read'], + }, + }, + async (context, request, response) => { + const cloudDefendContext = await context.cloudDefend; + try { + const status = await getCloudDefendStatus(cloudDefendContext); + return response.ok({ + body: status, + }); + } catch (err) { + cloudDefendContext.logger.error(`Error getting cloud_defend status`); + cloudDefendContext.logger.error(err); + + const error = transformError(err); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ); diff --git a/x-pack/plugins/cloud_defend/server/types.ts b/x-pack/plugins/cloud_defend/server/types.ts new file mode 100644 index 0000000000000..121a97ee926be --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/types.ts @@ -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 type { CloudSetup } from '@kbn/cloud-plugin/server'; +import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { + IRouter, + CustomRequestHandlerContext, + Logger, + SavedObjectsClientContract, + IScopedClusterClient, +} from '@kbn/core/server'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; +import type { + FleetStartContract, + FleetRequestHandlerContext, + AgentService, + PackageService, + AgentPolicyServiceInterface, + PackagePolicyClient, +} from '@kbn/fleet-plugin/server'; +import type { + PluginSetup as DataPluginSetup, + PluginStart as DataPluginStart, +} from '@kbn/data-plugin/server'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CloudDefendPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CloudDefendPluginStart {} + +export interface CloudDefendPluginSetupDeps { + data: DataPluginSetup; + security: SecurityPluginSetup; + cloud: CloudSetup; +} +export interface CloudDefendPluginStartDeps { + data: DataPluginStart; + fleet: FleetStartContract; + security: SecurityPluginStart; + licensing: LicensingPluginStart; +} + +export interface CloudDefendApiRequestHandlerContext { + user: ReturnType; + logger: Logger; + esClient: IScopedClusterClient; + soClient: SavedObjectsClientContract; + agentPolicyService: AgentPolicyServiceInterface; + agentService: AgentService; + packagePolicyService: PackagePolicyClient; + packageService: PackageService; +} + +export type CloudDefendRequestHandlerContext = CustomRequestHandlerContext<{ + cloudDefend: CloudDefendApiRequestHandlerContext; + fleet: FleetRequestHandlerContext['fleet']; +}>; + +/** + * Convenience type for routers in cloud_defend that includes the CloudDefendRequestHandlerContext type + * @internal + */ +export type CloudDefendRouter = IRouter; diff --git a/x-pack/plugins/cloud_defend/tsconfig.json b/x-pack/plugins/cloud_defend/tsconfig.json index f96b98d8c44dd..d12ee82da6fce 100755 --- a/x-pack/plugins/cloud_defend/tsconfig.json +++ b/x-pack/plugins/cloud_defend/tsconfig.json @@ -6,23 +6,30 @@ "include": [ "common/**/*", "public/**/*", + "server/**/*", "../../../typings/**/*", - "server/**/*.json", - "public/**/*.json" + "public/**/*.json", + "server/**/*.json" ], "kbn_references": [ "@kbn/core", + "@kbn/data-plugin", + "@kbn/security-plugin", "@kbn/fleet-plugin", - "@kbn/fleet-plugin", - "@kbn/core", "@kbn/i18n-react", + "@kbn/config-schema", + "@kbn/licensing-plugin", "@kbn/data-plugin", "@kbn/kibana-react-plugin", "@kbn/monaco", "@kbn/i18n", - "@kbn/shared-ux-router" + "@kbn/usage-collection-plugin", + "@kbn/cloud-plugin", + "@kbn/shared-ux-router", + "@kbn/shared-ux-link-redirect-app", + "@kbn/core-logging-server-mocks", + "@kbn/securitysolution-es-utils", + "@kbn/es-types" ], - "exclude": [ - "target/**/*" - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts index 86e0a4ec1d054..2daa273185357 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts @@ -16,6 +16,7 @@ import { CLOUDBEAT_VANILLA, CLOUDBEAT_GCP, CLOUDBEAT_AZURE, + CLOUDBEAT_VULN_MGMT_AWS, SUPPORTED_POLICY_TEMPLATES, SUPPORTED_CLOUDBEAT_INPUTS, } from '../../../common/constants'; @@ -52,6 +53,8 @@ const getPostureType = (policyTemplateInput: PostureInput) => { case CLOUDBEAT_VANILLA: case CLOUDBEAT_EKS: return 'kspm'; + case CLOUDBEAT_VULN_MGMT_AWS: + return 'vuln_mgmt'; default: return 'n/a'; } @@ -60,6 +63,7 @@ const getPostureType = (policyTemplateInput: PostureInput) => { const getDeploymentType = (policyTemplateInput: PostureInput) => { switch (policyTemplateInput) { case CLOUDBEAT_AWS: + case CLOUDBEAT_VULN_MGMT_AWS: return 'aws'; case CLOUDBEAT_AZURE: return 'azure'; diff --git a/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts b/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts index eecc50da0ba4c..2f902d03f62dc 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts @@ -10,14 +10,18 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { errors } from '@elastic/elasticsearch'; import { latestFindingsTransform } from './latest_findings_transform'; +const LATEST_TRANSFORM_V830 = 'cloud_security_posture.findings_latest-default-0.0.1'; +const LATEST_TRANSFORM_V840 = 'cloud_security_posture.findings_latest-default-8.4.0'; + +const PREVIOUS_TRANSFORMS = [LATEST_TRANSFORM_V830, LATEST_TRANSFORM_V840]; + // TODO: Move transforms to integration package export const initializeCspTransforms = async ( esClient: ElasticsearchClient, logger: Logger ): Promise => { // Deletes old assets from previous versions as part of upgrade process - const LATEST_TRANSFORM_V830 = 'cloud_security_posture.findings_latest-default-0.0.1'; - await deleteTransformSafe(esClient, logger, LATEST_TRANSFORM_V830); + await deletePreviousTransformsVersions(esClient, logger); await initializeTransform(esClient, latestFindingsTransform, logger); }; @@ -107,16 +111,30 @@ export const startTransformIfNotStarted = async ( } }; -const deleteTransformSafe = async (esClient: ElasticsearchClient, logger: Logger, name: string) => { +const deletePreviousTransformsVersions = async (esClient: ElasticsearchClient, logger: Logger) => { + for (const transform of PREVIOUS_TRANSFORMS) { + const response = await deleteTransformSafe(esClient, logger, transform); + if (response) return; + } +}; + +const deleteTransformSafe = async ( + esClient: ElasticsearchClient, + logger: Logger, + name: string +): Promise => { try { await esClient.transform.deleteTransform({ transform_id: name, force: true }); logger.info(`Deleted transform successfully [Name: ${name}]`); + return true; } catch (e) { if (e instanceof errors.ResponseError && e.statusCode === 404) { - logger.trace(`Transform no longer exists [Name: ${name}]`); + logger.trace(`Transform not exists [Name: ${name}]`); + return false; } else { logger.error(`Failed to delete transform [Name: ${name}]`); logger.error(e); + return false; } } }; diff --git a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts index 9775b260c6949..9e9192e7690e8 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts @@ -12,7 +12,7 @@ import { } from '../../common/constants'; export const latestFindingsTransform: TransformPutTransformRequest = { - transform_id: 'cloud_security_posture.findings_latest-default-8.4.0', + transform_id: 'cloud_security_posture.findings_latest-default-8.8.0', description: 'Defines findings transformation to view only the latest finding per resource', source: { index: FINDINGS_INDEX_PATTERN, @@ -30,7 +30,7 @@ export const latestFindingsTransform: TransformPutTransformRequest = { retention_policy: { time: { field: '@timestamp', - max_age: '5h', + max_age: '26h', }, }, latest: { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap deleted file mode 100644 index af4464cbc6b4e..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FieldTypeIcon render component when type matches a field type 1`] = ` - - - -`; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx index e01890d8b0a0f..04e8059163960 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx @@ -6,47 +6,60 @@ */ import React from 'react'; -import { mount, shallow } from 'enzyme'; - +import { render, fireEvent, waitFor } from '@testing-library/react'; import { FieldTypeIcon } from './field_type_icon'; import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants'; describe('FieldTypeIcon', () => { - test(`render component when type matches a field type`, () => { - const typeIconComponent = shallow( - + it('renders label and icon but not tooltip content on mouseover if tooltipEnabled=false', async () => { + const { getByText, container } = render( + ); - expect(typeIconComponent).toMatchSnapshot(); - }); - // TODO: Broken with Jest 27 - test.skip(`render with tooltip and test hovering`, () => { - // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers({ legacyFakeTimers: true }); + expect(container.querySelector('[data-test-subj="dvFieldTypeIcon-keyword"]')).toBeDefined(); - const typeIconComponent = mount( - - ); - - expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1); + fireEvent.mouseOver(getByText('Keyword')); - typeIconComponent.simulate('mouseover'); + await waitFor( + () => { + const tooltip = document.querySelector('[data-test-subj="dvFieldTypeTooltip"]'); + expect(tooltip).toBeNull(); + }, + { timeout: 1500 } // Account for long delay on tooltips + ); + }); - // Run the timers so the EuiTooltip will be visible - jest.runAllTimers(); + it(`renders component when type matches a field type`, () => { + const { container } = render( + + ); - typeIconComponent.update(); - expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(2); + expect(container.querySelector('[data-test-subj="dvFieldTypeIcon-keyword"]')).toBeDefined(); + expect(container).toHaveTextContent('keyword'); + }); - typeIconComponent.simulate('mouseout'); + it('shows tooltip content on mouseover', async () => { + const { getByText, container } = render( + + ); + expect(container.querySelector('[data-test-subj="dvFieldTypeIcon-keyword"]')).toBeDefined(); + expect(container).toHaveTextContent('keyword'); - // Run the timers so the EuiTooltip will be hidden again - jest.runAllTimers(); + fireEvent.mouseOver(getByText('keyword')); - typeIconComponent.update(); - expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(2); + await waitFor( + () => { + const tooltip = document.querySelector('[data-test-subj="dvFieldTypeTooltip"]'); + expect(tooltip).toBeVisible(); + expect(tooltip?.textContent).toEqual('Keyword'); + }, + { timeout: 1500 } // Account for long delay on tooltips + ); + fireEvent.mouseOut(getByText('keyword')); - // Clearing all mocks will also reset fake timers. - jest.clearAllMocks(); + await waitFor(() => { + const tooltip = document.querySelector('[data-test-subj="dvFieldTypeTooltip"]'); + expect(tooltip).toBeNull(); + }); }); }); diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.tsx index f7b7ed8142aac..1f77f0284803f 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.tsx @@ -27,8 +27,13 @@ export const FieldTypeIcon: FC = ({ tooltipEnabled = false, }); if (tooltipEnabled === true) { return ( - - + + ); } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts deleted file mode 100644 index bcf32a7f62bd7..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { lazyLoadModules } from '../../../lazy_load_bundle'; -import { GetTimeFieldRangeResponse } from '../../../../common/types/time_field_request'; - -export async function getTimeFieldRange({ - index, - timeFieldName, - query, - runtimeMappings, -}: { - index: string; - timeFieldName?: string; - query?: QueryDslQueryContainer; - runtimeMappings?: estypes.MappingRuntimeFields; -}) { - const body = JSON.stringify({ index, timeFieldName, query, runtimeMappings }); - const fileUploadModules = await lazyLoadModules(); - - return await fileUploadModules.getHttp().fetch({ - path: `/internal/file_upload/time_field_range`, - method: 'POST', - body, - }); -} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.ts new file mode 100644 index 0000000000000..340a089bf3c10 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { recreateCrawlerConnector } from './recreate_crawler_connector_api_logic'; + +describe('CreateCrawlerIndexApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('createCrawlerIndex', () => { + it('calls correct api', async () => { + const indexName = 'elastic-co-crawler'; + http.post.mockReturnValue(Promise.resolve({ connector_id: 'connectorId' })); + + const result = recreateCrawlerConnector({ indexName }); + await nextTick(); + + expect(http.post).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/elastic-co-crawler/crawler/connector' + ); + await expect(result).resolves.toEqual({ connector_id: 'connectorId' }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.ts new file mode 100644 index 0000000000000..6982850b00661 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.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 { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface RecreateCrawlerConnectorArgs { + indexName: string; +} + +export interface RecreateCrawlerConnectorResponse { + created: string; // the name of the newly created index +} + +export const recreateCrawlerConnector = async ({ indexName }: RecreateCrawlerConnectorArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/connector`; + + return await HttpLogic.values.http.post(route); +}; + +export const RecreateCrawlerConnectorApiLogic = createApiLogic( + ['recreate_crawler_connector_api_logic'], + recreateCrawlerConnector +); + +export type RecreateCrawlerConnectorActions = Actions< + RecreateCrawlerConnectorArgs, + RecreateCrawlerConnectorResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts index 7d9a7ede6ad98..ba3b551b23602 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { i18n } from '@kbn/i18n'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; @@ -14,9 +15,7 @@ export interface CreateCustomPipelineApiLogicArgs { indexName: string; } -export interface CreateCustomPipelineApiLogicResponse { - created: string[]; -} +export type CreateCustomPipelineApiLogicResponse = Record; export const createCustomPipeline = async ({ indexName, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts index 391e3e102383a..7c3f80b05e7a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface DeleteIndexApiLogicArgs { @@ -36,3 +36,5 @@ export const DeleteIndexApiLogic = createApiLogic(['delete_index_api_logic'], de }, }), }); + +export type DeleteIndexApiActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..2753fadb77aea --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.ts @@ -0,0 +1,20 @@ +/* + * 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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { revertConnectorPipeline } from './revert_connector_pipeline_api_logic'; + +describe('RevertConnectorPipelineApiLogic', () => { + it('should call delete pipeline endpoint', () => { + const { http } = mockHttpValues; + revertConnectorPipeline({ indexName: 'indexName' }); + expect(http.delete).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/indexName/pipelines' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts new file mode 100644 index 0000000000000..a97cafdcd1881 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts @@ -0,0 +1,26 @@ +/* + * 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 { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface RevertConnectorPipelineArgs { + indexName: string; +} + +export const revertConnectorPipeline = async ({ indexName }: RevertConnectorPipelineArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/pipelines`; + + return await HttpLogic.values.http.delete(route); +}; + +export const RevertConnectorPipelineApilogic = createApiLogic( + ['revert_connector_pipeline_api'], + revertConnectorPipeline +); + +export type RevertConnectorPipelineActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx index 0bf12fa6e200d..f6cb956478fde 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx @@ -18,7 +18,7 @@ import { SyncsContextMenu } from './syncs_context_menu'; export const getHeaderActions = (indexData?: ElasticsearchIndexWithIngestion) => { const ingestionMethod = getIngestionMethod(indexData); return [ - ...(isCrawlerIndex(indexData) ? [] : []), + ...(isCrawlerIndex(indexData) && indexData.connector ? [] : []), ...(isConnectorIndex(indexData) ? [] : []), { return ( <>
    - - + ]} + description={ +

    + +

    + } + title={

    {i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.title', { @@ -36,19 +47,9 @@ export const AuthenticationPanel: React.FC = () => { })}

    -
    - - - -
    - -

    - -

    -
    + } + /> + {isEditing ? : }
    {isModalVisible && } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx index 7eb4c04db4480..e2c3074f41750 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx @@ -30,6 +30,7 @@ describe('CrawlRulesTable', () => { crawlRules, domainId: '6113e1407a2f2e6f42489794', indexName, + title: 'Crawl rules', }; beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx index db98f1fb987f7..5c96617e1fb2d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx @@ -18,6 +18,7 @@ import { EuiLink, EuiSelect, EuiText, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -39,6 +40,7 @@ export interface CrawlRulesTableProps { description?: React.ReactNode; domainId: string; indexName: string; + title?: React.ReactNode; } export const getReadableCrawlerRule = (rule: CrawlerRules) => { @@ -99,17 +101,14 @@ const DEFAULT_DESCRIPTION = (

    - {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.descriptionLinkText', { - defaultMessage: 'Learn more about crawl rules', - })} - - ), - }} + defaultMessage="Create a crawl rule to include or exclude pages whose URL matches the rule. Rules run in sequential order, and each URL is evaluated according to the first match." /> + + + {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.descriptionLinkText', { + defaultMessage: 'Learn more about crawl rules', + })} +

    ); @@ -119,6 +118,7 @@ export const CrawlRulesTable: React.FC = ({ indexName, crawlRules, defaultCrawlRule, + title, }) => { const { updateCrawlRules } = useActions(CrawlerDomainDetailLogic); @@ -251,7 +251,7 @@ export const CrawlRulesTable: React.FC = ({ updateCrawlRules(newCrawlRules as CrawlRule[]); clearFlashMessages(); }} - title="" + title={title || ''} uneditableItems={defaultCrawlRule ? [defaultCrawlRule] : undefined} canRemoveLastItem /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx index 9ca36ba97f7b4..a00c353eaab0b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx @@ -43,14 +43,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

    - {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { - defaultMessage: 'Entry points', - })} -

    -
    - + +

    + {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { + defaultMessage: 'Entry points', + })} +

    + + } + /> ), id: CrawlerDomainTabId.ENTRY_POINTS, @@ -74,14 +80,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

    - {i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { - defaultMessage: 'Sitemaps', - })} -

    -
    - + +

    + {i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { + defaultMessage: 'Sitemaps', + })} +

    + + } + /> ), id: CrawlerDomainTabId.SITE_MAPS, @@ -93,18 +105,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

    - {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { - defaultMessage: 'Crawl rules', - })} -

    -
    +

    + {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { + defaultMessage: 'Crawl rules', + })} +

    + + } /> ), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx index 54c93ff744ede..a7ffe1bc81bd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx @@ -14,7 +14,6 @@ import { shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { - EuiButton, EuiButtonEmpty, EuiContextMenuItem, EuiPopover, @@ -26,6 +25,8 @@ import { import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; + import { rerender } from '../../../../../../test_helpers'; import { DeduplicationPanel } from './deduplication_panel'; @@ -61,8 +62,11 @@ describe('DeduplicationPanel', () => { it('contains a button to reset to defaults', () => { const wrapper = shallow(); + const dedupeButton = shallow( +
    {wrapper.find(PageIntroduction).prop('actions')}
    + ).children(); - wrapper.find('EuiFlexGroup').first().dive().find(EuiButton).simulate('click'); + dedupeButton.simulate('click'); expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenCalledWith({ fields: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx index e17815765169e..c4bedce7a43c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx @@ -21,7 +21,6 @@ import { EuiSelectable, EuiSpacer, EuiSwitch, - EuiText, EuiTitle, } from '@elastic/eui'; @@ -31,6 +30,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../../../shared/doc_links'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic'; import { getCheckedOptionLabels, getSelectableOptions } from './utils'; @@ -55,8 +55,8 @@ export const DeduplicationPanel: React.FC = () => { return (
    - - +

    {i18n.translate('xpack.enterpriseSearch.crawler.deduplicationPanel.title', { @@ -64,8 +64,8 @@ export const DeduplicationPanel: React.FC = () => { })}

    -
    - + } + actions={ { } )} - -
    - - -

    - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.deduplicationPanel.learnMoreMessage', - { - defaultMessage: 'Learn more about content hashing', - } - )} - - ), - }} - /> -

    -
    + documents on this domain." + /> +

    + } + links={ + + {i18n.translate('xpack.enterpriseSearch.crawler.deduplicationPanel.learnMoreMessage', { + defaultMessage: 'Learn more about content hashing', + })} + + } + /> = ({ domain, indexName, items }) => { +export const EntryPointsTable: React.FC = ({ + domain, + indexName, + items, + title, +}) => { const { onAdd, onDelete, onUpdate } = useActions(EntryPointsTableLogic); const field = 'value'; @@ -76,7 +82,8 @@ export const EntryPointsTable: React.FC = ({ domain, inde {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.description', { defaultMessage: 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.', - })}{' '} + })} + {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.learnMoreLinkText', { defaultMessage: 'Learn more about entry points.', @@ -130,7 +137,7 @@ export const EntryPointsTable: React.FC = ({ domain, inde onAdd={onAdd} onDelete={onDelete} onUpdate={onUpdate} - title="" + title={title} disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx index b8fdab12acbc6..0887db23c7789 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx @@ -13,9 +13,8 @@ import { EuiButton, EuiConfirmModal, EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, EuiLink, + EuiFlexGroup, EuiSpacer, EuiText, EuiTitle, @@ -26,6 +25,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { CANCEL_BUTTON_LABEL } from '../../../../../../shared/constants'; import { docLinks } from '../../../../../../shared/doc_links'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; import { EditExtractionRule } from './edit_extraction_rule'; import { ExtractionRulesLogic } from './extraction_rules_logic'; @@ -81,56 +81,56 @@ export const ExtractionRules: React.FC = () => { )} - - -

    - {i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.title', { - defaultMessage: 'Extraction rules', - })} -

    -
    -
    - {extractionRules.length === 0 ? ( - <> - ) : ( - - + +

    + {i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.title', { + defaultMessage: 'Extraction rules', + })} +

    + + } + description={ +

    + +

    + } + links={ + {i18n.translate( - 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.addRuleLabel', + 'xpack.enterpriseSearch.content.crawler.extractionRules.learnMoreLink', { - defaultMessage: 'Add extraction rule', + defaultMessage: 'Learn more about content extraction rules.', } )} -
    -
    - )} +
    + } + actions={ + extractionRules.length === 0 + ? [] + : [ + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.addRuleLabel', + { + defaultMessage: 'Add extraction rule', + } + )} + , + ] + } + /> - - -

    - - {i18n.translate( - 'xpack.enterpriseSearch.content.crawler.extractionRules.learnMoreLink', - { - defaultMessage: 'Learn more about content extraction rules.', - } - )} - - ), - }} - /> -

    -
    - + {editingExtractionRule ? ( = ({ domain, indexName, items }) => { +export const SitemapsTable: React.FC = ({ + domain, + indexName, + items, + title, +}) => { const { updateSitemaps } = useActions(CrawlerDomainDetailLogic); const field = 'url'; @@ -113,7 +119,7 @@ export const SitemapsTable: React.FC = ({ domain, indexName, updateSitemaps(newSitemaps as Sitemap[]); clearFlashMessages(); }} - title="" + title={title || ''} disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx new file mode 100644 index 0000000000000..ea6dc5d7d2b62 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx @@ -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 React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Status } from '../../../../../../common/types/api'; + +import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { DeleteIndexModal } from '../../search_indices/delete_index_modal'; +import { IndicesLogic } from '../../search_indices/indices_logic'; +import { IndexViewLogic } from '../index_view_logic'; + +import { NoConnectorRecordLogic } from './no_connector_record_logic'; + +export const NoConnectorRecord: React.FC = () => { + const { indexName } = useValues(IndexViewLogic); + const { isDeleteLoading } = useValues(IndicesLogic); + const { openDeleteModal } = useActions(IndicesLogic); + const { makeRequest } = useActions(RecreateCrawlerConnectorApiLogic); + const { status } = useValues(RecreateCrawlerConnectorApiLogic); + NoConnectorRecordLogic.mount(); + const buttonsDisabled = status === Status.LOADING || isDeleteLoading; + + return ( + <> + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.title', + { + defaultMessage: "This index's connector configuration has been removed", + } + )} + + } + body={ +

    + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.description', + { + defaultMessage: + 'We could not find a connector configuration for this crawler index. The record should be recreated, or the index should be deleted.', + } + )} +

    + } + actions={[ + makeRequest({ indexName })} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.recreateConnectorRecord', + { + defaultMessage: 'Recreate connector record', + } + )} + , + openDeleteModal(indexName)} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.deleteIndex', + { + defaultMessage: 'Delete index', + } + )} + , + ]} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts new file mode 100644 index 0000000000000..3d9ce1c235fb4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../../__mocks__/kea_logic'; + +import { KibanaLogic } from '../../../../shared/kibana'; + +import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { DeleteIndexApiLogic } from '../../../api/index/delete_index_api_logic'; +import { SEARCH_INDICES_PATH } from '../../../routes'; + +import { NoConnectorRecordLogic } from './no_connector_record_logic'; + +describe('NoConnectorRecordLogic', () => { + const { mount: deleteMount } = new LogicMounter(DeleteIndexApiLogic); + const { mount: recreateMount } = new LogicMounter(RecreateCrawlerConnectorApiLogic); + const { mount } = new LogicMounter(NoConnectorRecordLogic); + beforeEach(() => { + deleteMount(); + recreateMount(); + mount(); + }); + it('should redirect to search indices on delete', () => { + KibanaLogic.values.navigateToUrl = jest.fn(); + DeleteIndexApiLogic.actions.apiSuccess({} as any); + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(SEARCH_INDICES_PATH); + }); + it('should fetch index on recreate', () => { + NoConnectorRecordLogic.actions.fetchIndex = jest.fn(); + RecreateCrawlerConnectorApiLogic.actions.apiSuccess({} as any); + expect(NoConnectorRecordLogic.actions.fetchIndex).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.ts new file mode 100644 index 0000000000000..a3d1d1a653ec0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.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. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { KibanaLogic } from '../../../../shared/kibana'; + +import { + RecreateCrawlerConnectorActions, + RecreateCrawlerConnectorApiLogic, +} from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { + DeleteIndexApiActions, + DeleteIndexApiLogic, +} from '../../../api/index/delete_index_api_logic'; +import { SEARCH_INDICES_PATH } from '../../../routes'; +import { IndexViewActions, IndexViewLogic } from '../index_view_logic'; + +type NoConnectorRecordActions = RecreateCrawlerConnectorActions['apiSuccess'] & { + deleteSuccess: DeleteIndexApiActions['apiSuccess']; + fetchIndex: IndexViewActions['fetchIndex']; +}; + +export const NoConnectorRecordLogic = kea>({ + connect: { + actions: [ + RecreateCrawlerConnectorApiLogic, + ['apiSuccess'], + IndexViewLogic, + ['fetchIndex'], + DeleteIndexApiLogic, + ['apiSuccess as deleteSuccess'], + ], + }, + listeners: ({ actions }) => ({ + apiSuccess: () => { + actions.fetchIndex(); + }, + deleteSuccess: () => { + KibanaLogic.values.navigateToUrl(SEARCH_INDICES_PATH); + }, + }), + path: ['enterprise_search', 'content', 'no_connector_record'], +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx index 96d213ced0f2d..e95fb524f29ab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx @@ -9,20 +9,32 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiButtonEmpty, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; +import { Status } from '../../../../../../../common/types/api'; + +import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; + import { KibanaLogic } from '../../../../../shared/kibana'; import { LicensingLogic } from '../../../../../shared/licensing'; import { CreateCustomPipelineApiLogic } from '../../../../api/index/create_custom_pipeline_api_logic'; +import { RevertConnectorPipelineApilogic } from '../../../../api/pipelines/revert_connector_pipeline_api_logic'; import { IndexViewLogic } from '../../index_view_logic'; +import { PipelinesLogic } from '../pipelines_logic'; export const CustomizeIngestPipelineItem: React.FC = () => { const { indexName, ingestionMethod } = useValues(IndexViewLogic); const { isCloud } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); + const { status: createStatus } = useValues(CreateCustomPipelineApiLogic); + const { isDeleteModalOpen, hasIndexIngestionPipeline } = useValues(PipelinesLogic); + const { closeDeleteModal, openDeleteModal } = useActions(PipelinesLogic); + const { makeRequest: revertPipeline } = useActions(RevertConnectorPipelineApilogic); + const { status: revertStatus } = useValues(RevertConnectorPipelineApilogic); const isGated = !isCloud && !hasPlatinumLicense; return ( @@ -49,17 +61,61 @@ export const CustomizeIngestPipelineItem: React.FC = () => { )} - createCustomPipeline({ indexName })} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.copyButtonLabel', - { defaultMessage: 'Copy and customize' } - )} - + {isDeleteModalOpen && ( + revertPipeline({ indexName })} + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.deleteModal.confirmButton', + { + defaultMessage: 'Delete pipeline', + } + )} + buttonColor="danger" + > +

    + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.deleteModal.description', + { + defaultMessage: + 'This will delete any custom pipelines associated with this index, including machine learning inference pipelines. The index will revert to using the default ingest pipeline.', + } + )} +

    +
    + )} + {hasIndexIngestionPipeline ? ( + openDeleteModal()} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.revertPipelineLabel', + { defaultMessage: 'Delete custom pipeline' } + )} + + ) : ( + createCustomPipeline({ indexName })} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.copyButtonLabel', + { defaultMessage: 'Copy and customize' } + )} + + )}
    diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx index 0ca347da0712e..a6c68b47c7b5a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx @@ -12,8 +12,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; - import { DEFAULT_PIPELINE_NAME } from '../../../../../../../common/constants'; import { CustomPipelineItem } from './custom_pipeline_item'; @@ -52,28 +50,4 @@ describe('IngestPipelinesCard', () => { expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(1); expect(wrapper.find(CustomPipelineItem)).toHaveLength(0); }); - it('does not render customize cta with index ingest pipeline', () => { - const pipelineName = crawlerIndex.name; - const pipelines: Record = { - [pipelineName]: {}, - [`${pipelineName}@custom`]: { - processors: [], - }, - }; - setMockValues({ - ...DEFAULT_VALUES, - data: pipelines, - hasIndexIngestionPipeline: true, - pipelineName, - pipelineState: { - ...DEFAULT_VALUES.pipelineState, - name: pipelineName, - }, - }); - - const wrapper = shallow(); - expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(0); - expect(wrapper.find(DefaultPipelineItem)).toHaveLength(1); - expect(wrapper.find(CustomPipelineItem)).toHaveLength(1); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx index d57b4142d2905..64f195e217891 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx @@ -24,14 +24,8 @@ import { IngestPipelineFlyout } from './ingest_pipeline_flyout'; export const IngestPipelinesCard: React.FC = () => { const { indexName, ingestionMethod } = useValues(IndexViewLogic); - const { - canSetPipeline, - hasIndexIngestionPipeline, - index, - pipelineName, - pipelineState, - showPipelineSettings, - } = useValues(PipelinesLogic); + const { canSetPipeline, index, pipelineName, pipelineState, showPipelineSettings } = + useValues(PipelinesLogic); const { closePipelineSettings, openPipelineSettings, setPipelineState, savePipeline } = useActions(PipelinesLogic); const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); @@ -45,7 +39,7 @@ export const IngestPipelinesCard: React.FC = () => { return ( <> - {!hasIndexIngestionPipeline && } + {showPipelineSettings && ( { - const { showAddMlInferencePipelineModal, hasIndexIngestionPipeline, index, pipelineName } = - useValues(PipelinesLogic); + const { + showMissingPipelineCallout, + showAddMlInferencePipelineModal, + hasIndexIngestionPipeline, + index, + pipelineName, + } = useValues(PipelinesLogic); const { closeAddMlInferencePipelineModal, openAddMlInferencePipelineModal } = useActions(PipelinesLogic); + const { indexName } = useValues(IndexNameLogic); + const { makeRequest: revertPipeline } = useActions(RevertConnectorPipelineApilogic); const apiIndex = isApiIndex(index); const pipelinesTabs: EuiTabbedContentTab[] = [ @@ -66,6 +78,36 @@ export const SearchIndexPipelines: React.FC = () => { return ( <> + {showMissingPipelineCallout && ( + +

    + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.missingPipeline.description', + { + defaultMessage: + 'The custom pipeline for this index has been deleted. This may affect connector data ingestion. Its configuration will need to be reverted to the default pipeline settings.', + } + )} +

    + revertPipeline({ indexName })}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.missingPipeline.buttonLabel', + { + defaultMessage: 'Revert pipeline to default', + } + )} + +
    + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index a16bd36ee54ba..69e01d45750fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -35,10 +35,12 @@ const DEFAULT_VALUES = { hasIndexIngestionPipeline: false, index: undefined, indexName: '', + isDeleteModalOpen: false, mlInferencePipelineProcessors: undefined, pipelineName: DEFAULT_PIPELINE_VALUES.name, pipelineState: DEFAULT_PIPELINE_VALUES, showAddMlInferencePipelineModal: false, + showMissingPipelineCallout: false, showPipelineSettings: false, }; @@ -145,16 +147,16 @@ describe('PipelinesLogic', () => { }); }); describe('createCustomPipelineSuccess', () => { - it('should call flashSuccessToast', () => { + it('should call flashSuccessToast and update pipelines', () => { PipelinesLogic.actions.setPipelineState = jest.fn(); PipelinesLogic.actions.savePipeline = jest.fn(); PipelinesLogic.actions.fetchCustomPipeline = jest.fn(); PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex); - PipelinesLogic.actions.createCustomPipelineSuccess({ created: ['a', 'b'] }); + PipelinesLogic.actions.createCustomPipelineSuccess({ [connectorIndex.name]: {} }); expect(flashSuccessToast).toHaveBeenCalledWith('Custom pipeline created'); expect(PipelinesLogic.actions.setPipelineState).toHaveBeenCalledWith({ ...PipelinesLogic.values.pipelineState, - name: 'a', + name: connectorIndex.name, }); expect(PipelinesLogic.actions.savePipeline).toHaveBeenCalled(); expect(PipelinesLogic.actions.fetchCustomPipeline).toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index dbe0239028ad5..bf71dc215243f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -66,6 +66,10 @@ import { } from '../../../api/pipelines/detach_ml_inference_pipeline'; import { FetchMlInferencePipelineProcessorsApiLogic } from '../../../api/pipelines/fetch_ml_inference_pipeline_processors'; +import { + RevertConnectorPipelineActions, + RevertConnectorPipelineApilogic, +} from '../../../api/pipelines/revert_connector_pipeline_api_logic'; import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../../utils/indices'; type PipelinesActions = Pick< @@ -77,6 +81,7 @@ type PipelinesActions = Pick< AttachMlInferencePipelineResponse >['apiSuccess']; closeAddMlInferencePipelineModal: () => void; + closeDeleteModal: () => void; closePipelineSettings: () => void; createCustomPipeline: Actions< CreateCustomPipelineApiLogicArgs, @@ -132,7 +137,9 @@ type PipelinesActions = Pick< fetchMlInferenceProcessors: typeof FetchMlInferencePipelineProcessorsApiLogic.actions.makeRequest; fetchMlInferenceProcessorsApiError: (error: HttpError) => HttpError; openAddMlInferencePipelineModal: () => void; + openDeleteModal: () => void; openPipelineSettings: () => void; + revertPipelineSuccess: RevertConnectorPipelineActions['apiSuccess']; savePipeline: () => void; setPipelineState(pipeline: IngestPipelineParams): { pipeline: IngestPipelineParams; @@ -148,18 +155,22 @@ interface PipelinesValues { hasIndexIngestionPipeline: boolean; index: CachedFetchIndexApiLogicValues['fetchIndexApiData']; indexName: string; + isDeleteModalOpen: boolean; mlInferencePipelineProcessors: InferencePipeline[]; pipelineName: string; pipelineState: IngestPipelineParams; showAddMlInferencePipelineModal: boolean; + showMissingPipelineCallout: boolean; showPipelineSettings: boolean; } export const PipelinesLogic = kea>({ actions: { closeAddMlInferencePipelineModal: true, + closeDeleteModal: true, closePipelineSettings: true, openAddMlInferencePipelineModal: true, + openDeleteModal: true, openPipelineSettings: true, savePipeline: true, setPipelineState: (pipeline: IngestPipelineParams) => ({ pipeline }), @@ -201,6 +212,8 @@ export const PipelinesLogic = kea { - actions.setPipelineState({ ...values.pipelineState, name: created[0] }); + createCustomPipelineSuccess: (created) => { + actions.fetchCustomPipelineSuccess(created); + actions.setPipelineState({ ...values.pipelineState, name: values.indexName }); actions.savePipeline(); actions.fetchCustomPipeline({ indexName: values.index.name }); }, @@ -313,6 +327,19 @@ export const PipelinesLogic = kea { + if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { + if (values.index.connector) { + // had to split up these if checks rather than nest them or typescript wouldn't recognize connector as defined + actions.fetchIndexApiSuccess({ + ...values.index, + connector: { ...values.index.connector, pipeline: values.defaultPipelineValues }, + }); + actions.fetchCustomPipelineSuccess({}); + } + } + actions.fetchCustomPipeline({ indexName: values.indexName }); + }, savePipeline: () => { if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { if (values.index.connector) { @@ -326,6 +353,14 @@ export const PipelinesLogic = kea ({ + isDeleteModalOpen: [ + false, + { + closeDeleteModal: () => false, + openDeleteModal: () => true, + revertPipelineSuccess: () => false, + }, + ], pipelineState: [ DEFAULT_PIPELINE_VALUES, { @@ -381,5 +416,25 @@ export const PipelinesLogic = kea customPipelineData && customPipelineData[indexName] ? indexName : pipelineState.name, ], + showMissingPipelineCallout: [ + () => [ + selectors.hasIndexIngestionPipeline, + selectors.pipelineName, + selectors.customPipelineData, + selectors.index, + ], + ( + hasCustomPipeline: boolean, + pipelineName: string, + customPipelineData: Record | undefined, + index: ElasticsearchIndexWithIngestion + ) => + Boolean( + hasCustomPipeline && + customPipelineData && + !customPipelineData[pipelineName] && + isConnectorIndex(index) + ), + ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index 0643cce0f8173..52ae6628f8081 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -38,6 +38,7 @@ import { AutomaticCrawlScheduler } from './crawler/automatic_crawl_scheduler/aut import { CrawlCustomSettingsFlyout } from './crawler/crawl_custom_settings_flyout/crawl_custom_settings_flyout'; import { CrawlerConfiguration } from './crawler/crawler_configuration/crawler_configuration'; import { SearchIndexDomainManagement } from './crawler/domain_management/domain_management'; +import { NoConnectorRecord } from './crawler/no_connector_record'; import { SearchIndexDocuments } from './documents'; import { SearchIndexIndexMappings } from './index_mappings'; import { IndexNameLogic } from './index_name_logic'; @@ -224,12 +225,16 @@ export const SearchIndex: React.FC = () => { rightSideItems: getHeaderActions(index), }} > - <> - {indexName === index?.name && ( - - )} - {isCrawlerIndex(index) && } - + {isCrawlerIndex(index) && !index.connector ? ( + + ) : ( + <> + {indexName === index?.name && ( + + )} + {isCrawlerIndex(index) && } + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts index 16ee5faff914a..89043f1d7c0c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts @@ -85,7 +85,7 @@ describe('IndicesLogic', () => { describe('openDeleteModal', () => { it('should set deleteIndexName and set isDeleteModalVisible to true', () => { IndicesLogic.actions.fetchIndexDetails = jest.fn(); - IndicesLogic.actions.openDeleteModal(connectorIndex); + IndicesLogic.actions.openDeleteModal(connectorIndex.name); expect(IndicesLogic.values).toEqual({ ...DEFAULT_VALUES, deleteModalIndexName: 'connector', @@ -98,7 +98,7 @@ describe('IndicesLogic', () => { }); describe('closeDeleteModal', () => { it('should set deleteIndexName to empty and set isDeleteModalVisible to false', () => { - IndicesLogic.actions.openDeleteModal(connectorIndex); + IndicesLogic.actions.openDeleteModal(connectorIndex.name); IndicesLogic.actions.fetchIndexDetails = jest.fn(); IndicesLogic.actions.closeDeleteModal(); expect(IndicesLogic.values).toEqual({ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts index 953fce853904b..9489a005d0b15 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts @@ -69,7 +69,7 @@ export interface IndicesActions { }): { meta: Meta; returnHiddenIndices: boolean; searchQuery?: string }; makeRequest: typeof FetchIndicesAPILogic.actions.makeRequest; onPaginate(newPageIndex: number): { newPageIndex: number }; - openDeleteModal(index: ElasticsearchViewIndex): { index: ElasticsearchViewIndex }; + openDeleteModal(indexName: string): { indexName: string }; setIsFirstRequest(): void; } export interface IndicesValues { @@ -102,7 +102,7 @@ export const IndicesLogic = kea>({ searchQuery, }), onPaginate: (newPageIndex) => ({ newPageIndex }), - openDeleteModal: (index) => ({ index }), + openDeleteModal: (indexName) => ({ indexName }), setIsFirstRequest: true, }, connect: { @@ -137,8 +137,8 @@ export const IndicesLogic = kea>({ await breakpoint(150); actions.makeRequest(input); }, - openDeleteModal: ({ index }) => { - actions.fetchIndexDetails({ indexName: index.name }); + openDeleteModal: ({ indexName }) => { + actions.fetchIndexDetails({ indexName }); }, }), path: ['enterprise_search', 'content', 'indices_logic'], @@ -147,7 +147,7 @@ export const IndicesLogic = kea>({ '', { closeDeleteModal: () => '', - openDeleteModal: (_, { index: { name } }) => name, + openDeleteModal: (_, { indexName }) => indexName, }, ], isDeleteModalVisible: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx index fd0bc55fdc7e3..c73f567f31edb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx @@ -38,7 +38,7 @@ interface IndicesTableProps { isLoading?: boolean; meta: Meta; onChange: (criteria: CriteriaWithPagination) => void; - onDelete: (index: ElasticsearchViewIndex) => void; + onDelete: (indexName: string) => void; } export const IndicesTable: React.FC = ({ @@ -175,7 +175,7 @@ export const IndicesTable: React.FC = ({ }, } ), - onClick: (index) => onDelete(index), + onClick: (index) => onDelete(index.name), type: 'icon', }, ], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts index fd87b60bd99a8..f88620faf5b16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts @@ -81,7 +81,7 @@ export function getIngestionStatus(index?: ElasticsearchIndexWithIngestion): Ing if (!index || isApiIndex(index)) { return IngestionStatus.CONNECTED; } - if (isConnectorIndex(index) || isCrawlerIndex(index)) { + if (isConnectorIndex(index) || (isCrawlerIndex(index) && index.connector)) { if ( index.connector.last_seen && moment(index.connector.last_seen).isBefore(moment().subtract(30, 'minutes')) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx new file mode 100644 index 0000000000000..4077dac076839 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx @@ -0,0 +1,129 @@ +/* + * 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 { mount } from 'enzyme'; + +import { EuiLink } from '@elastic/eui'; + +import { PageIntroduction } from './page_introduction'; + +describe('PageIntroduction component', () => { + it('renders with title as a string', () => { + const wrapper = mount(); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper + .find('[data-test-subj="pageIntroductionTitleContainer"]') + .hostNodes(); + expect(titleContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual('string title'); + }); + + it('renders title as React node', () => { + const wrapper = mount( + react node title} + description="some description" + /> + ); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper.find('[data-test-subj="injected"]').hostNodes(); + expect(titleContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual('react node title'); + }); + + it('renders with description only', () => { + const wrapper = mount(); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper + .find('[data-test-subj="pageIntroductionTitleContainer"]') + .hostNodes(); + + const descriptionContainer = wrapper + .find('[data-test-subj="pageIntroductionDescriptionText"]') + .hostNodes(); + expect(titleContainer).toHaveLength(1); + expect(descriptionContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual(''); + expect(descriptionContainer.text()).toEqual('some description'); + }); + + it('renders with single link', () => { + const wrapper = mount( + + test link to nowhere + + } + /> + ); + const links = wrapper.find(EuiLink); + expect(links).toHaveLength(1); + expect(links.prop('href')).toEqual('testlink'); + // due to accesibility injections text includes screen reader text as well + expect(links.text().startsWith('test link to nowhere')).toBe(true); + }); + + it('renders with multiple links', () => { + const wrapper = mount( + + test link to nowhere + , + + test link to nowhere2 + , + ]} + /> + ); + const links = wrapper.find(EuiLink); + expect(links).toHaveLength(2); + expect(links.at(0).prop('href')).toEqual('testlink'); + // due to accesibility injections text includes screen reader text as well + expect(links.at(0).text().startsWith('test link to nowhere')).toBe(true); + expect(links.at(1).prop('href')).toEqual('testlink2'); + // due to accesibility injections text includes screen reader text as well + expect(links.at(1).text().startsWith('test link to nowhere2')).toBe(true); + }); + + it('renders with single actions', () => { + const wrapper = mount( + some action} + /> + ); + const actions = wrapper.find('button'); + expect(actions).toHaveLength(1); + expect(actions.text()).toEqual('some action'); + }); + + it('renders with multiple action', () => { + const wrapper = mount( + some action, ]} + /> + ); + const actions = wrapper.find('button'); + expect(actions).toHaveLength(2); + expect(actions.at(0).text()).toEqual('some action'); + expect(actions.at(1).text()).toEqual('another action'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.tsx new file mode 100644 index 0000000000000..1f2e5951bb94f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.tsx @@ -0,0 +1,69 @@ +/* + * 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 { EuiText, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; + +export interface PageIntroductionProps { + actions?: React.ReactNode | React.ReactNode[]; + description: React.ReactNode | string; + links?: React.ReactNode | React.ReactNode[]; + title?: string | React.ReactNode; +} + +export const PageIntroduction: React.FC = ({ + actions, + description, + links, + title = '', +}) => { + return ( + + + + + {typeof title === 'string' ? ( + +

    {title}

    +
    + ) : ( + title + )} +
    + + + + {description} + + + {!!links && ( + <> + + + + {Array.isArray(links) ? links.map((link) => link) : links} + + + + )} +
    +
    + + + {Array.isArray(actions) + ? actions?.map((action, index) => ( + + {action} + + )) + : actions} + + +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx index 9725abcd6eba7..3d2a243664edf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { BindLogic } from 'kea'; +import { PageIntroduction } from '../../page_introduction/page_introduction'; import { ReorderableTable } from '../reorderable_table'; jest.mock('./get_updated_columns', () => ({ @@ -86,18 +87,19 @@ describe('InlineEditableTable', () => { it('renders a ReorderableTable', () => { const wrapper = shallow(); const reorderableTable = wrapper.find(ReorderableTable); + const addButton = shallow( +
    {wrapper.find(PageIntroduction).prop('actions')}
    + ).children(); expect(reorderableTable.exists()).toBe(true); expect(reorderableTable.prop('items')).toEqual(items); - expect( - wrapper.find('[data-test-subj="inlineEditableTableActionButton"]').children().text() - ).toEqual('New row'); + expect(addButton.children().text()).toEqual('New row'); }); it('renders a title if one is provided', () => { const wrapper = shallow( Some Description

    } /> ); - expect(wrapper.find('[data-test-subj="inlineEditableTableTitle"]').exists()).toBe(true); + expect(wrapper.find(PageIntroduction).prop('title')).toEqual(requiredParams.title); }); it('does not render a title if none is provided', () => { @@ -114,7 +116,7 @@ describe('InlineEditableTable', () => { const wrapper = shallow( Some Description

    } /> ); - expect(wrapper.find('[data-test-subj="inlineEditableTableDescription"]').exists()).toBe(true); + expect(wrapper.find(PageIntroduction).prop('description')).toEqual(

    Some Description

    ); }); it('renders no description if none is provided', () => { @@ -141,9 +143,10 @@ describe('InlineEditableTable', () => { const wrapper = shallow( ); - expect( - wrapper.find('[data-test-subj="inlineEditableTableActionButton"]').children().text() - ).toEqual('Add a new row custom text'); + const addButton = shallow( +
    {wrapper.find(PageIntroduction).prop('actions')}
    + ).children(); + expect(addButton.children().text()).toEqual('Add a new row custom text'); }); describe('when a user is editing an unsaved item', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx index 232ad491b1397..d14acc91ac581 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx @@ -11,9 +11,11 @@ import classNames from 'classnames'; import { useActions, useValues, BindLogic } from 'kea'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { PageIntroduction } from '../../page_introduction/page_introduction'; + import { ReorderableTable } from '../reorderable_table'; import { ItemWithAnID } from '../types'; @@ -29,7 +31,7 @@ export interface InlineEditableTableProps { items: Item[]; defaultItem?: Partial; emptyPropertyAllowed?: boolean; - title: string; + title: string | React.ReactNode; addButtonText?: string; canRemoveLastItem?: boolean; className?: string; @@ -129,28 +131,10 @@ export const InlineEditableTableContents = ({ return ( <> - - - {!!title && ( - -

    {title}

    -
    - )} - {!!description && ( - <> - - - {description} - - - )} -
    - + ({ i18n.translate('xpack.enterpriseSearch.inlineEditableTable.newRowButtonLabel', { defaultMessage: 'New row', })} - - -
    - + , + ]} + /> + { + const mockClient = { + asCurrentUser: { + index: jest.fn(), + }, + asInternalUser: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should recreate connector document', async () => { + mockClient.asCurrentUser.index.mockResolvedValue({ _id: 'connectorId' }); + + await recreateConnectorDocument(mockClient as unknown as IScopedClusterClient, 'indexName'); + expect(mockClient.asCurrentUser.index).toHaveBeenCalledWith({ + document: { + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'indexName', + is_native: false, + language: '', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: null, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: 'elastic-crawler', + status: ConnectorStatus.CONFIGURED, + sync_now: false, + }, + index: CONNECTORS_INDEX, + refresh: 'wait_for', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.ts b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.ts new file mode 100644 index 0000000000000..17bf6945d0d82 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.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 { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '../..'; + +import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../common/constants'; +import { ConnectorStatus } from '../../../common/types/connectors'; + +import { createConnectorDocument } from '../../utils/create_connector_document'; + +export const recreateConnectorDocument = async ( + client: IScopedClusterClient, + indexName: string +) => { + const document = createConnectorDocument({ + indexName, + isNative: false, + // The search index has already been created so we don't need the language, which we can't retrieve anymore anyway + language: '', + pipeline: null, + serviceType: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, + }); + const result = await client.asCurrentUser.index({ + document: { ...document, status: ConnectorStatus.CONFIGURED }, + index: CONNECTORS_INDEX, + refresh: 'wait_for', + }); + return result._id; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 23232fda4199d..91859d66f6e55 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -8,8 +8,6 @@ import { merge } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; -import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; - import { createIndexPipelineDefinitions } from './create_pipeline_definitions'; import { formatMlPipelineBody } from './create_pipeline_definitions'; @@ -22,19 +20,13 @@ describe('createIndexPipelineDefinitions util function', () => { }, }; - const expectedResult = { - created: [indexName, `${indexName}@custom`, getInferencePipelineNameFromIndexName(indexName)], - }; - beforeEach(() => { jest.clearAllMocks(); }); it('should create the pipelines', async () => { mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); - await expect( - createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient) - ).resolves.toEqual(expectedResult); + await createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient); expect(mockClient.ingest.putPipeline).toHaveBeenCalledTimes(3); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index 8b511ab22c3e7..209d9d4787ea3 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { generateMlInferencePipelineBody } from '../../../common/ml_inference_pipeline'; @@ -14,10 +15,6 @@ import { } from '../../../common/types/pipelines'; import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; -export interface CreatedPipelines { - created: string[]; -} - /** * Used to create index-specific Ingest Pipelines to be used in conjunction with Enterprise Search * ingestion mechanisms. Three pipelines are created: @@ -33,189 +30,202 @@ export interface CreatedPipelines { export const createIndexPipelineDefinitions = async ( indexName: string, esClient: ElasticsearchClient -): Promise => { +): Promise> => { // TODO: add back descriptions (see: https://github.com/elastic/elasticsearch-specification/issues/1827) - await esClient.ingest.putPipeline({ - description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, - id: getInferencePipelineNameFromIndexName(indexName), - processors: [], - version: 1, - }); - await esClient.ingest.putPipeline({ - description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, - id: `${indexName}@custom`, - processors: [], - version: 1, - }); - await esClient.ingest.putPipeline({ - _meta: { - managed: true, - managed_by: 'Enterprise Search', - }, - description: `Enterprise Search ingest pipeline for the '${indexName}' index`, - id: `${indexName}`, - processors: [ - { - attachment: { - field: '_attachment', - if: 'ctx?._extract_binary_content == true', - ignore_missing: true, - indexed_chars_field: '_attachment_indexed_chars', - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - [ - "Processor 'attachment' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + let result: Record = {}; + try { + const mlPipeline = { + description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, + id: getInferencePipelineNameFromIndexName(indexName), + processors: [], + version: 1, + }; + await esClient.ingest.putPipeline(mlPipeline); + result = { ...result, [mlPipeline.id]: mlPipeline }; + const customPipeline = { + description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, + id: `${indexName}@custom`, + processors: [], + version: 1, + }; + await esClient.ingest.putPipeline(customPipeline); + result = { ...result, [customPipeline.id]: customPipeline }; + const ingestPipeline = { + _meta: { + managed: true, + managed_by: 'Enterprise Search', + }, + description: `Enterprise Search ingest pipeline for the '${indexName}' index`, + id: `${indexName}`, + processors: [ + { + attachment: { + field: '_attachment', + if: 'ctx?._extract_binary_content == true', + ignore_missing: true, + indexed_chars_field: '_attachment_indexed_chars', + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + [ + "Processor 'attachment' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], ], - ], + }, }, - }, - ], - target_field: '_extracted_attachment', + ], + target_field: '_extracted_attachment', + }, }, - }, - { - set: { - field: 'body', - if: 'ctx?._extract_binary_content == true', - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - [ - "Processor 'set' with tag 'set_body' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + { + set: { + field: 'body', + if: 'ctx?._extract_binary_content == true', + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + [ + "Processor 'set' with tag 'set_body' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], ], - ], + }, }, - }, - ], - tag: 'set_body', - value: '{{{_extracted_attachment.content}}}', + ], + tag: 'set_body', + value: '{{{_extracted_attachment.content}}}', + }, }, - }, - { - pipeline: { - if: 'ctx?._run_ml_inference == true', - name: getInferencePipelineNameFromIndexName(indexName), - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'pipeline' with tag 'index_ml_inference_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + pipeline: { + if: 'ctx?._run_ml_inference == true', + name: getInferencePipelineNameFromIndexName(indexName), + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'pipeline' with tag 'index_ml_inference_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'index_ml_inference_pipeline', + ], + tag: 'index_ml_inference_pipeline', + }, }, - }, - { - pipeline: { - name: `${indexName}@custom`, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'pipeline' with tag 'index_custom_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + pipeline: { + name: `${indexName}@custom`, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'pipeline' with tag 'index_custom_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'index_custom_pipeline', + ], + tag: 'index_custom_pipeline', + }, }, - }, - { - gsub: { - field: 'body', - if: 'ctx?._extract_binary_content == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'gsub' with tag 'remove_replacement_chars' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + gsub: { + field: 'body', + if: 'ctx?._extract_binary_content == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'gsub' with tag 'remove_replacement_chars' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - pattern: '�', - replacement: '', - tag: 'remove_replacement_chars', + ], + pattern: '�', + replacement: '', + tag: 'remove_replacement_chars', + }, }, - }, - { - gsub: { - field: 'body', - if: 'ctx?._reduce_whitespace == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'gsub' with tag 'remove_extra_whitespace' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + gsub: { + field: 'body', + if: 'ctx?._reduce_whitespace == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'gsub' with tag 'remove_extra_whitespace' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - pattern: '\\s+', - replacement: ' ', - tag: 'remove_extra_whitespace', + ], + pattern: '\\s+', + replacement: ' ', + tag: 'remove_extra_whitespace', + }, }, - }, - { - trim: { - field: 'body', - if: 'ctx?._reduce_whitespace == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'trim' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + trim: { + field: 'body', + if: 'ctx?._reduce_whitespace == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'trim' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], + ], + }, }, - }, - { - remove: { - field: [ - '_attachment', - '_attachment_indexed_chars', - '_extracted_attachment', - '_extract_binary_content', - '_reduce_whitespace', - '_run_ml_inference', - ], - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'remove' with tag 'remove_meta_fields' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + remove: { + field: [ + '_attachment', + '_attachment_indexed_chars', + '_extracted_attachment', + '_extract_binary_content', + '_reduce_whitespace', + '_run_ml_inference', + ], + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'remove' with tag 'remove_meta_fields' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'remove_meta_fields', + ], + tag: 'remove_meta_fields', + }, }, - }, - ], - version: 1, - }); - return { - created: [indexName, `${indexName}@custom`, getInferencePipelineNameFromIndexName(indexName)], - }; + ], + version: 1, + }; + await esClient.ingest.putPipeline(ingestPipeline); + result = { ...result, [ingestPipeline.id]: ingestPipeline }; + return result; + } catch (error) { + // clean up pipelines if one failed to create + for (const id of Object.keys(result)) { + await esClient.ingest.deletePipeline({ id }); + } + throw error; + } }; /** diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.ts new file mode 100644 index 0000000000000..3038f48004ea9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.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 { IScopedClusterClient } from '@kbn/core/server'; + +import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; + +export const deleteIndexPipelines = async ( + client: IScopedClusterClient, + indexName: string +): Promise<{ deleted: string[] }> => { + const deleted: string[] = []; + const promises = [ + client.asCurrentUser.ingest + .deletePipeline({ id: indexName }) + .then(() => deleted.push(indexName)), + client.asCurrentUser.ingest + .deletePipeline({ + id: getInferencePipelineNameFromIndexName(indexName), + }) + .then(() => deleted.push(getInferencePipelineNameFromIndexName(indexName))), + client.asCurrentUser.ingest + .deletePipeline({ id: `${indexName}@custom` }) + .then(() => deleted.push(`${indexName}@custom`)), + ]; + await Promise.allSettled(promises); + return { + deleted, + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts new file mode 100644 index 0000000000000..60a9e5cfcf97d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IScopedClusterClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '../..'; + +import { fetchConnectorByIndexName } from '../connectors/fetch_connectors'; + +import { deleteIndexPipelines } from './delete_pipelines'; + +import { getDefaultPipeline } from './get_default_pipeline'; + +export const revertCustomPipeline = async (client: IScopedClusterClient, indexName: string) => { + const connector = await fetchConnectorByIndexName(client, indexName); + if (connector) { + const pipeline = await getDefaultPipeline(client); + await client.asCurrentUser.update({ + doc: { pipeline }, + id: connector?.id, + index: CONNECTORS_INDEX, + }); + } + return await deleteIndexPipelines(client, indexName); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts index 08cea961709fc..8d0c1e73df848 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts @@ -16,6 +16,7 @@ import { addConnector } from '../../../lib/connectors/add_connector'; import { deleteConnectorById } from '../../../lib/connectors/delete_connector'; import { fetchConnectorByIndexName } from '../../../lib/connectors/fetch_connectors'; import { fetchCrawlerByIndexName } from '../../../lib/crawler/fetch_crawlers'; +import { recreateConnectorDocument } from '../../../lib/crawler/post_connector'; import { updateHtmlExtraction } from '../../../lib/crawler/put_html_extraction'; import { deleteIndex } from '../../../lib/indices/delete_index'; import { RouteDependencies } from '../../../plugin'; @@ -429,6 +430,37 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { }) ); + router.post( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/connector', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const connector = await fetchConnectorByIndexName(client, request.params.indexName); + if (connector) { + return createError({ + errorCode: ErrorCode.CONNECTOR_DOCUMENT_ALREADY_EXISTS, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.recreateConnector.connectorExistsError', + { + defaultMessage: 'A connector for this index already exists', + } + ), + response, + statusCode: 409, + }); + } + + const connectorId = await recreateConnectorDocument(client, request.params.indexName); + return response.ok({ body: { connector_id: connectorId } }); + }) + ); + registerCrawlerCrawlRulesRoutes(routeDependencies); registerCrawlerEntryPointRoutes(routeDependencies); registerCrawlerSitemapRoutes(routeDependencies); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index 238a882142b7c..5bf70def81b76 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -41,9 +41,11 @@ import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_infere import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; +import { deleteIndexPipelines } from '../../lib/pipelines/delete_pipelines'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { getPipeline } from '../../lib/pipelines/get_pipeline'; import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; +import { revertCustomPipeline } from '../../lib/pipelines/revert_custom_pipeline'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; @@ -196,6 +198,8 @@ export function registerIndexRoutes({ await deleteConnectorById(client, connector.id); } + await deleteIndexPipelines(client, indexName); + await client.asCurrentUser.indices.delete({ index: indexName }); return response.ok({ @@ -299,6 +303,27 @@ export function registerIndexRoutes({ }) ); + router.delete( + { + path: '/internal/enterprise_search/indices/{indexName}/pipelines', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + const body = await revertCustomPipeline(client, indexName); + + return response.ok({ + body, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.get( { path: '/internal/enterprise_search/indices/{indexName}/pipelines', diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts new file mode 100644 index 0000000000000..62851572cf798 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts @@ -0,0 +1,195 @@ +/* + * 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 { ConnectorStatus } from '../../common/types/connectors'; + +import { createConnectorDocument } from './create_connector_document'; + +describe('createConnectorDocument', () => { + it('should create a connector document', () => { + expect( + createConnectorDocument({ + indexName: 'indexName', + isNative: false, + language: 'fr', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + }) + ).toEqual({ + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'indexName', + is_native: false, + language: 'fr', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: null, + status: ConnectorStatus.CREATED, + sync_now: false, + }); + }); + it('should remove search- from name', () => { + expect( + createConnectorDocument({ + indexName: 'search-indexName', + isNative: false, + language: 'fr', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + }) + ).toEqual({ + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'search-indexName', + is_native: false, + language: 'fr', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: null, + status: ConnectorStatus.CREATED, + sync_now: false, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts new file mode 100644 index 0000000000000..d4776e3941cee --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ConnectorDocument, + ConnectorStatus, + FilteringPolicy, + FilteringRuleRule, + FilteringValidationState, + IngestPipelineParams, +} from '../../common/types/connectors'; + +export function createConnectorDocument({ + indexName, + isNative, + pipeline, + serviceType, + language, +}: { + indexName: string; + isNative: boolean; + language: string | null; + pipeline?: IngestPipelineParams | null; + serviceType?: string | null; +}): ConnectorDocument { + const currentTimestamp = new Date().toISOString(); + return { + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + }, + ], + index_name: indexName, + is_native: isNative, + language, + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: indexName.startsWith('search-') ? indexName.substring(7) : indexName, + pipeline, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: serviceType || null, + status: ConnectorStatus.CREATED, + sync_now: false, + }; +} diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index 9d9c6c9720de4..2881076c7f156 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -13,7 +13,7 @@ ## Fleet Requirements -Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag `--xpack.fleet.agents.tlsCheckDisabled=false`) +Fleet needs to have Elasticsearch API keys enabled. Also you need to configure the hosts your agent is going to use to comunication with Elasticsearch and Kibana (Not needed if you use Elastic cloud). You can use the following flags: @@ -26,27 +26,53 @@ Also you need to configure the hosts your agent is going to use to comunication ### Getting started -See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-kibana) +See the [Contributing to Kibana documentation](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) or head straight to the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/getting-started/welcome) for setting up your dev environment, run Elasticsearch and start Kibana. -One common development workflow is: +This plugin follows the `common`, `server`, `public` structure described in the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro). Refer to [The anatomy of a plugin](https://docs.elastic.dev/kibana-dev-docs/key-concepts/anatomy-of-a-plugin) in the guide for further details. -- Bootstrap Kibana - ``` - yarn kbn bootstrap +We follow the pattern of developing feature branches under your personal fork of Kibana. Refer to [Set up a Development Environment](https://docs.elastic.dev/kibana-dev-docs/getting-started/setup-dev-env) in the guide for further details. Other best practices including developer principles, standards and style guide can be found under the Contributing section of the guide. + +Note: The plugin was previously named Ingest Manager, it's possible that some variables are still named with that old plugin name. + +#### Dev environment setup + +These are some additional recommendations to the steps detailed in the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/getting-started/setup-dev-env). + +1. Create a `config/kibana.dev.yml` file by copying the existing `config/kibana.yml` file. +2. It is recommended to explicitly set a base path for Kibana (refer to [Considerations for basepath](https://www.elastic.co/guide/en/kibana/current/development-basepath.html) for details). To do this, add the following to your `kibana.dev.yml`: + ```yml + server.basePath: / ``` -- Start Elasticsearch in one shell + where `yourPath` is a path of your choice (e.g. your name). +3. Bootstrap Kibana: + ``` + yarn kbn bootstrap + ``` + +#### Running Elasticsearch and Kibana +- Start Elasticsearch in one shell (NB: you might want to add other flags to enable data persistency and/or running Fleet Server locally, see below): ``` yarn es snapshot -E xpack.security.authc.api_key.enabled=true -E xpack.security.authc.token.enabled=true ``` -- Start Kibana in another shell +- Start Kibana in another shell: ``` - yarn start --no-base-path + yarn start ``` + If you don't have a base path set up, add `--no-base-path` to `yarn start`. + +#### Useful tips + +If Kibana fails to start, it is possible that your local setup got corrupted. An easy fix is to run: +``` +yarn kbn clean && yarn kbn bootstrap +``` -This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide -](https://github.com/elastic/kibana/blob/main/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. +To avoid losing all your data when you restart Elasticsearch, you can provide a path to store the data when running the `yarn es snapshot ` command, e.g.: +``` +-E path.data=/tmp/es-data +``` -Note: The plugin was previously named Ingest Manager it's possible that some variables are still named with that old plugin name. +Refer to the [Running Elasticsearch during development](https://www.elastic.co/guide/en/kibana/current/running-elasticsearch.html) page of the guide for other options. ### Running Fleet Server Locally in a Container @@ -99,7 +125,7 @@ docker run -e KIBANA_HOST=http://{YOUR-IP}:5601/{BASE-PATH} -e KIBANA_USERNAME=e Ensure you provide the `-p 8220:8220` port mapping to map the Fleet Server container's port `8220` to your local machine's port `8220` in order for Fleet to communicate with Fleet Server. -For the latest version, use `8.0.0-SNAPSHOT`. Otherwise, you can explore the available versions at https://www.docker.elastic.co/r/beats/elastic-agent. +Explore the available versions at https://www.docker.elastic.co/r/beats/elastic-agent. Only released versions are shown by default: tick the `Include snapshots` checkbox to see the latest version, e.g. `8.8.0-SNAPSHOT`. Once the Fleet Server container is running, you should be able to treat it as if it were a local process running on `https://localhost:8220` when configuring Fleet via the UI. You can then run `elastic-agent` on your local machine directly for testing purposes, or with Docker (recommended) see next section. @@ -115,6 +141,18 @@ Once the Fleet Server container is running, you should be able to treat it as if ### Tests +#### Unit tests + +Kibana primarily uses Jest for unit testing. Each plugin or package defines a `jest.config.js` that extends a preset provided by the `@kbn/test` package. Unless you intend to run all unit tests within the project, you should provide the Jest configuration for Fleet. The following command runs all Fleet unit tests: +``` +yarn jest --config x-pack/plugins/fleet/jest.config.js +``` + +You can also run a specific test by passing the filepath as an argument, e.g.: +``` +yarn jest --config x-pack/plugins/fleet/jest.config.js x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +``` + #### API integration tests You need to have `docker` to run ingest manager api integration tests diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 8fae99c53ded7..aae45e12c1ddd 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -19,9 +19,10 @@ export const allowedExperimentalValues = Object.freeze({ experimentalDataStreamSettings: false, displayAgentMetrics: true, showIntegrationsSubcategories: false, - agentFqdnMode: true, + agentFqdnMode: false, showExperimentalShipperOptions: false, fleetServerStandalone: false, + agentTamperProtectionEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index eb7dcf0cad55c..4ff0b522325ca 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -20,10 +20,67 @@ } ], "paths": { + "/health_check": { + "post": { + "summary": "Fleet Server health check", + "tags": [ + "Fleet internals" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "host": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "fleet-server-health-check", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + } + } + } + } + } + }, "/setup": { "post": { - "summary": "Setup", - "tags": [], + "summary": "Initiate Fleet setup", + "tags": [ + "Fleet internals" + ], "responses": { "200": { "description": "OK", @@ -64,8 +121,10 @@ }, "/settings": { "get": { - "summary": "Settings", - "tags": [], + "summary": "Get settings", + "tags": [ + "Fleet internals" + ], "responses": { "200": { "description": "OK", @@ -84,8 +143,10 @@ "operationId": "get-settings" }, "put": { - "summary": "Settings - Update", - "tags": [], + "summary": "Update settings", + "tags": [ + "Fleet internals" + ], "requestBody": { "content": { "application/json": { @@ -128,63 +189,12 @@ "operationId": "update-settings" } }, - "/health_check": { - "post": { - "summary": "Fleet Server Health Check", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "host": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/error" - } - }, - "operationId": "fleet-server-health-check", - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "host": { - "type": "string" - } - } - } - } - } - } - } - }, "/service-tokens": { "post": { - "summary": "Generate service tokens", - "tags": [], + "summary": "Create service token", + "tags": [ + "Service tokens" + ], "responses": { "200": { "description": "OK", @@ -219,8 +229,10 @@ }, "/service_tokens": { "post": { - "summary": "Generate service tokens", - "tags": [], + "summary": "Create service token", + "tags": [ + "Service tokens" + ], "responses": { "200": { "description": "OK", @@ -255,7 +267,9 @@ "/epm/verification_key_id": { "get": { "summary": "Get package signature verification key ID", - "tags": [], + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -291,37 +305,14 @@ }, "operationId": "packages-get-verification-key-id" }, - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "pkgName", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string" - }, - "name": "pkgVersion", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string" - }, - "name": "filePath", - "in": "path", - "required": true - } - ] + "parameters": [] }, "/epm/categories": { "get": { - "summary": "Package categories", - "tags": [], + "summary": "List package categories", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -347,7 +338,7 @@ "type": "boolean", "default": false }, - "description": "Whether to include prerelease packages in categories count (e.g. beta, rc, preview) " + "description": "Whether to include prerelease packages in categories count (e.g. beta, rc, preview)" }, { "in": "query", @@ -370,8 +361,10 @@ }, "/epm/packages/limited": { "get": { - "summary": "Packages - Get limited list", - "tags": [], + "summary": "Get limited package list", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -401,8 +394,10 @@ }, "/epm/packages": { "get": { - "summary": "Packages - List", - "tags": [], + "summary": "List packages", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -436,7 +431,7 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" }, { "in": "query", @@ -457,8 +452,10 @@ ] }, "post": { - "summary": "Packages - Install by upload", - "tags": [], + "summary": "Install by package by direct upload", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -544,8 +541,10 @@ }, "/epm/packages/_bulk": { "post": { - "summary": "Packages - Bulk install", - "tags": [], + "summary": "Bulk install packages", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -570,7 +569,7 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "requestBody": { @@ -602,8 +601,10 @@ }, "/epm/packages/{pkgkey}": { "get": { - "summary": "Packages - Info", - "tags": [], + "summary": "Get package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -670,14 +671,16 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "deprecated": true }, "post": { - "summary": "Packages - Install", - "tags": [], + "summary": "Install package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -755,8 +758,10 @@ "deprecated": true }, "delete": { - "summary": "Packages - Delete", - "tags": [], + "summary": "Delete ackage", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -835,8 +840,10 @@ }, "/epm/packages/{pkgName}/{pkgVersion}": { "get": { - "summary": "Packages - Info", - "tags": [], + "summary": "Get package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -940,12 +947,14 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "post": { - "summary": "Packages - Install", - "tags": [], + "summary": "Install package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1030,8 +1039,10 @@ } }, "put": { - "summary": "Packages - Update", - "tags": [], + "summary": "Update package settings", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1095,8 +1106,10 @@ } }, "delete": { - "summary": "Packages - Delete", - "tags": [], + "summary": "Delete package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1166,8 +1179,10 @@ }, "/epm/packages/{pkgName}/{pkgVersion}/{filePath}": { "get": { - "summary": "Packages - Get file from registry", - "tags": [], + "summary": "Get package file", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1225,8 +1240,10 @@ }, "/epm/packages/{pkgName}/stats": { "get": { - "summary": "Get stats for a package", - "tags": [], + "summary": "Get package stats", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1270,8 +1287,10 @@ }, "/agents/setup": { "get": { - "summary": "Agents setup - Info", - "tags": [], + "summary": "Get agent setup info", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1295,7 +1314,10 @@ ] }, "post": { - "summary": "Agents setup - Create", + "summary": "Initiate agent setup", + "tags": [ + "Agents" + ], "operationId": "setup-agents", "responses": { "200": { @@ -1342,8 +1364,10 @@ }, "/agent-status": { "get": { - "summary": "Agents - Summary stats", - "tags": [], + "summary": "Get agent status summary", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1411,8 +1435,10 @@ }, "/agent_status": { "get": { - "summary": "Agents - Summary stats", - "tags": [], + "summary": "Get agent status summary", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1499,8 +1525,10 @@ }, "/agent_status/data": { "get": { - "summary": "Agents - Get incoming data", - "tags": [], + "summary": "Get incoming agent data", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1550,8 +1578,10 @@ }, "/agents": { "get": { - "summary": "Agents - List", - "tags": [], + "summary": "List agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1611,8 +1641,10 @@ }, "/agents/bulk_upgrade": { "post": { - "summary": "Agents - Bulk Upgrade", - "tags": [], + "summary": "Bulk upgrade agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1663,7 +1695,10 @@ }, "/agents/action_status": { "get": { - "summary": "Agents - Action status", + "summary": "Get agent action status", + "tags": [ + "Agent actions" + ], "parameters": [ { "$ref": "#/components/parameters/page_size" @@ -1774,8 +1809,10 @@ } ], "get": { - "summary": "Agent - Info", - "tags": [], + "summary": "Get agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1807,8 +1844,10 @@ ] }, "put": { - "summary": "Agent - Update", - "tags": [], + "summary": "Update agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1861,8 +1900,10 @@ } }, "delete": { - "summary": "Agent - Delete", - "tags": [], + "summary": "Delete agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1909,8 +1950,10 @@ } ], "post": { - "summary": "Agent - Actions", - "tags": [], + "summary": "Create agent action", + "tags": [ + "Agent actions" + ], "responses": { "200": { "description": "OK", @@ -1983,8 +2026,10 @@ } ], "post": { - "summary": "Agent - Cancel Action", - "tags": [], + "summary": "Cancel agent action", + "tags": [ + "Agent actions" + ], "responses": { "200": { "description": "OK", @@ -2033,8 +2078,10 @@ } ], "get": { - "summary": "Get agent upload file", - "tags": [], + "summary": "Get file uploaded by agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2079,8 +2126,10 @@ } ], "post": { - "summary": "Agent - Reassign", - "tags": [], + "summary": "Reassign agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2122,8 +2171,10 @@ } }, "put": { - "summary": "Agent - Reassign", - "tags": [], + "summary": "Reassign agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2178,8 +2229,10 @@ } ], "post": { - "summary": "Agent - Unenroll", - "tags": [], + "summary": "Unenroll agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2253,8 +2306,10 @@ } ], "post": { - "summary": "Agent - Upgrade", - "tags": [], + "summary": "Upgrade agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2301,7 +2356,9 @@ ], "get": { "summary": "List agent uploads", - "tags": [], + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2335,8 +2392,10 @@ }, "/agents/bulk_reassign": { "post": { - "summary": "Agents - Bulk reassign", - "tags": [], + "summary": "Bulk reassign agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2405,8 +2464,10 @@ }, "/agents/bulk_unenroll": { "post": { - "summary": "Agents - Bulk unenroll", - "tags": [], + "summary": "Bulk unenroll agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2482,8 +2543,10 @@ }, "/agents/bulk_update_agent_tags": { "post": { - "summary": "Agents - Bulk update tags", - "tags": [], + "summary": "Bulk update agent tags", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2570,8 +2633,10 @@ }, "/agents/tags": { "get": { - "summary": "Agent Tags - List", - "description": "List all agent tags", + "summary": "List agent tags", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2602,8 +2667,10 @@ } ], "post": { - "summary": "Agent - Request Diagnostics", - "tags": [], + "summary": "Request agent diagnostics", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2634,8 +2701,10 @@ }, "/agents/bulk_request_diagnostics": { "post": { - "summary": "Agent - Bulk Request Diagnostics", - "tags": [], + "summary": "Bulk request diagnostics from agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2701,8 +2770,10 @@ }, "/agent_policies": { "get": { - "summary": "Agent policies - List", - "tags": [], + "summary": "List agent policies", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2772,8 +2843,10 @@ "description": "" }, "post": { - "summary": "Agent policy - Create", - "tags": [], + "summary": "Create agent policy", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2824,8 +2897,10 @@ } ], "get": { - "summary": "Agent policy - Info", - "tags": [], + "summary": "Get agent policy by ID", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2854,8 +2929,10 @@ "parameters": [] }, "put": { - "summary": "Agent policy - Update", - "tags": [], + "summary": "Update agent policy by ID", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2908,7 +2985,10 @@ } ], "post": { - "summary": "Agent policy - copy one policy", + "summary": "Copy agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-copy", "parameters": [ { @@ -2963,7 +3043,10 @@ }, "/agent_policies/{agentPolicyId}/full": { "get": { - "summary": "Agent policy - Get full policy", + "summary": "Get full agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-full", "responses": { "200": { @@ -3030,7 +3113,10 @@ }, "/agent_policies/{agentPolicyId}/download": { "get": { - "summary": "Agent policy - Download", + "summary": "Download agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-download", "responses": { "200": { @@ -3090,8 +3176,10 @@ }, "/agent_policies/_bulk_get": { "post": { - "summary": "Agent policies - Bulk Get", - "tags": [], + "summary": "Bulk get agent policies", + "tags": [ + "Agent policies" + ], "requestBody": { "content": { "application/json": { @@ -3153,7 +3241,10 @@ }, "/agent_policies/delete": { "post": { - "summary": "Agent policy - Delete", + "summary": "Delete agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "delete-agent-policy", "responses": { "200": { @@ -3209,8 +3300,10 @@ }, "/data_streams": { "get": { - "summary": "Data streams - List", - "tags": [], + "summary": "List data streams", + "tags": [ + "Data streams" + ], "responses": { "200": { "description": "OK", @@ -3240,8 +3333,10 @@ }, "/enrollment-api-keys": { "get": { - "summary": "Enrollment API Keys - List", - "tags": [], + "summary": "List enrollment API keys", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3292,8 +3387,10 @@ "deprecated": true }, "post": { - "summary": "Enrollment API Key - Create", - "tags": [], + "summary": "Create enrollment API key", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3341,8 +3438,10 @@ } ], "get": { - "summary": "Enrollment API Key - Info", - "tags": [], + "summary": "Get enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3370,8 +3469,10 @@ "deprecated": true }, "delete": { - "summary": "Enrollment API Key - Delete", - "tags": [], + "summary": "Delete enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3409,8 +3510,10 @@ }, "/enrollment_api_keys": { "get": { - "summary": "Enrollment API Keys - List", - "tags": [], + "summary": "List enrollment API keys", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3460,8 +3563,10 @@ "parameters": [] }, "post": { - "summary": "Enrollment API Key - Create", - "tags": [], + "summary": "Create enrollment API key", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3508,8 +3613,10 @@ } ], "get": { - "summary": "Enrollment API Key - Info", - "tags": [], + "summary": "Get enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3536,8 +3643,10 @@ "operationId": "get-enrollment-api-key" }, "delete": { - "summary": "Enrollment API Key - Delete", - "tags": [], + "summary": "Delete enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3574,8 +3683,10 @@ }, "/package_policies": { "get": { - "summary": "Package policies - List", - "tags": [], + "summary": "List package policies", + "tags": [ + "Package policies" + ], "responses": { "200": { "description": "OK", @@ -3617,7 +3728,10 @@ }, "parameters": [], "post": { - "summary": "Package policy - Create", + "summary": "Create package policy", + "tags": [ + "Package policies" + ], "operationId": "create-package-policy", "responses": { "200": { @@ -3661,8 +3775,10 @@ }, "/package_policies/_bulk_get": { "post": { - "summary": "Package policies - Bulk Get", - "tags": [], + "summary": "Bulk get package policies", + "tags": [ + "Package policies" + ], "requestBody": { "content": { "application/json": { @@ -3720,7 +3836,10 @@ }, "/package_policies/delete": { "post": { - "summary": "Package policy - Delete", + "summary": "Delete package policy", + "tags": [ + "Package policies" + ], "operationId": "post-delete-package-policy", "requestBody": { "content": { @@ -3787,7 +3906,10 @@ }, "/package_policies/upgrade": { "post": { - "summary": "Package policy - Upgrade", + "summary": "Upgrade package policy to a newer package version", + "tags": [ + "Package policies" + ], "operationId": "upgrade-package-policy", "requestBody": { "content": { @@ -3846,7 +3968,10 @@ }, "/package_policies/upgrade/dryrun": { "post": { - "summary": "Package policy - Upgrade Dry run", + "summary": "Dry run package policy upgrade", + "tags": [ + "Package policies" + ], "operationId": "upgrade-package-policy-dry-run", "requestBody": { "content": { @@ -3907,8 +4032,10 @@ }, "/package_policies/{packagePolicyId}": { "get": { - "summary": "Package policy - Info", - "tags": [], + "summary": "Get package policy by ID", + "tags": [ + "Package policies" + ], "responses": { "200": { "description": "OK", @@ -3945,7 +4072,10 @@ } ], "put": { - "summary": "Package policy - Update", + "summary": "Update package policy by ID", + "tags": [ + "Package policies" + ], "operationId": "update-package-policy", "requestBody": { "content": { @@ -3990,8 +4120,10 @@ ] }, "delete": { - "summary": "Package policy - Delete", - "tags": [], + "summary": "Delete package policy by ID", + "tags": [ + "Package policies" + ], "operationId": "delete-package-policy", "responses": { "200": { @@ -4029,8 +4161,10 @@ }, "/outputs": { "get": { - "summary": "Outputs", - "tags": [], + "summary": "List outputs", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4066,9 +4200,10 @@ "operationId": "get-outputs" }, "post": { - "summary": "Outputs", - "description": "Create a new output", - "tags": [], + "summary": "Create output", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4139,8 +4274,10 @@ }, "/outputs/{outputId}": { "get": { - "summary": "Output - Info", - "tags": [], + "summary": "Get output by ID", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4177,7 +4314,10 @@ } ], "delete": { - "summary": "Output - Delete", + "summary": "Delete output by ID", + "tags": [ + "Outputs" + ], "operationId": "delete-output", "responses": { "200": { @@ -4209,7 +4349,10 @@ ] }, "put": { - "summary": "Output - Update", + "summary": "Update output by ID", + "tags": [ + "Outputs" + ], "operationId": "update-output", "requestBody": { "content": { @@ -4286,10 +4429,46 @@ ] } }, + "/logstash_api_keys": { + "post": { + "summary": "Generate Logstash API key", + "tags": [ + "Outputs" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "api_key": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "generate-logstash-api-key", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ] + } + }, "/agent_download_sources": { "get": { - "summary": "Agent Download Sources", - "tags": [], + "summary": "List agent binary download sources", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4325,9 +4504,10 @@ "operationId": "get-download-sources" }, "post": { - "summary": "Agent Download Sources", - "description": "Create a new agent download source", - "tags": [], + "summary": "Create agent binary download source", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4381,8 +4561,10 @@ }, "/agent_download_sources/{sourceId}": { "get": { - "summary": "Agent Download Sources - Info", - "tags": [], + "summary": "Get agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4419,7 +4601,10 @@ } ], "delete": { - "summary": "Agent Download Sources - Delete", + "summary": "Delete agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "operationId": "delete-download-source", "responses": { "200": { @@ -4451,7 +4636,10 @@ ] }, "put": { - "summary": "Agent Download Sources - Update", + "summary": "Update agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "operationId": "update-download-source", "requestBody": { "content": { @@ -4508,43 +4696,12 @@ ] } }, - "/logstash_api_keys": { - "post": { - "summary": "Generate Logstash API key", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "api_key": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/error" - } - }, - "operationId": "generate-logstash-api-key", - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ] - } - }, "/fleet_server_hosts": { "get": { - "summary": "Fleet Server Hosts - List", - "description": "Return a list of Fleet server hosts", - "tags": [], + "summary": "List Fleet Server hosts", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4580,9 +4737,10 @@ "operationId": "get-fleet-server-hosts" }, "post": { - "summary": "Fleet Server Hosts - Create", - "description": "Create a new Fleet Server Host", - "tags": [], + "summary": "Create Fleet Server host", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4638,8 +4796,10 @@ }, "/fleet_server_hosts/{itemId}": { "get": { - "summary": "Fleet Server Hosts - Info", - "tags": [], + "summary": "Get Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4676,7 +4836,10 @@ } ], "delete": { - "summary": "Fleet Server Hosts - Delete", + "summary": "Delete Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "operationId": "delete-fleet-server-hosts", "responses": { "200": { @@ -4708,7 +4871,10 @@ ] }, "put": { - "summary": "Fleet Server Hosts - Update", + "summary": "Update Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "operationId": "update-fleet-server-hosts", "requestBody": { "content": { @@ -4765,9 +4931,10 @@ }, "/proxies": { "get": { - "summary": "Fleet Proxies - List", - "description": "Return a list of Proxies", - "tags": [], + "summary": "List proxies", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4803,9 +4970,10 @@ "operationId": "get-fleet-proxies" }, "post": { - "summary": "Fleet Proxies - Create", - "description": "Create a new Fleet Server Host", - "tags": [], + "summary": "Create proxy", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4867,8 +5035,10 @@ }, "/proxies/{itemId}": { "get": { - "summary": "Fleet Proxies - Info", - "tags": [], + "summary": "Get proxy by ID", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4905,7 +5075,10 @@ } ], "delete": { - "summary": "Fleet Proxies - Delete", + "summary": "Delete proxy by ID", + "tags": [ + "Proxies" + ], "operationId": "delete-fleet-proxies", "responses": { "200": { @@ -4937,7 +5110,10 @@ ] }, "put": { - "summary": "Fleet Proxies - Update", + "summary": "Update proxy by ID", + "tags": [ + "Proxies" + ], "operationId": "update-fleet-proxies", "requestBody": { "content": { @@ -5000,8 +5176,10 @@ }, "/kubernetes": { "get": { - "summary": "Get K8s Full Agent Manifest", - "tags": [], + "summary": "Get full K8s agent manifest", + "tags": [ + "Kubernetes" + ], "responses": { "200": { "description": "OK", @@ -5154,6 +5332,29 @@ } } }, + "responses": { + "error": { + "description": "Generic Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "type": "number" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + }, "schemas": { "fleet_setup_response": { "title": "Fleet Setup response", @@ -7151,29 +7352,6 @@ "url" ] } - }, - "responses": { - "error": { - "description": "Generic Error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "statusCode": { - "type": "number" - }, - "error": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } - } - } - } } }, "security": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 5f67c8630b0dc..9d6ae17a04a41 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -13,10 +13,44 @@ servers: - url: http://localhost:5601/api/fleet description: local paths: + /health_check: + post: + summary: Fleet Server health check + tags: + - Fleet internals + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + name: + type: string + status: + type: string + host: + type: string + '400': + $ref: '#/components/responses/error' + operationId: fleet-server-health-check + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + host: + type: string /setup: post: - summary: Setup - tags: [] + summary: Initiate Fleet setup + tags: + - Fleet internals responses: '200': description: OK @@ -40,8 +74,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /settings: get: - summary: Settings - tags: [] + summary: Get settings + tags: + - Fleet internals responses: '200': description: OK @@ -53,8 +88,9 @@ paths: $ref: '#/components/responses/error' operationId: get-settings put: - summary: Settings - Update - tags: [] + summary: Update settings + tags: + - Fleet internals requestBody: content: application/json: @@ -80,42 +116,11 @@ paths: '400': $ref: '#/components/responses/error' operationId: update-settings - /health_check: - post: - summary: Fleet Server Health Check - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - name: - type: string - status: - type: string - host: - type: string - '400': - $ref: '#/components/responses/error' - operationId: fleet-server-health-check - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - host: - type: string /service-tokens: post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK @@ -136,8 +141,9 @@ paths: deprecated: true /service_tokens: post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK @@ -158,7 +164,8 @@ paths: /epm/verification_key_id: get: summary: Get package signature verification key ID - tags: [] + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -183,26 +190,12 @@ paths: '400': $ref: '#/components/responses/error' operationId: packages-get-verification-key-id - parameters: - - schema: - type: string - name: pkgName - in: path - required: true - - schema: - type: string - name: pkgVersion - in: path - required: true - - schema: - type: string - name: filePath - in: path - required: true + parameters: [] /epm/categories: get: - summary: Package categories - tags: [] + summary: List package categories + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -221,7 +214,7 @@ paths: default: false description: >- Whether to include prerelease packages in categories count (e.g. beta, - rc, preview) + rc, preview) - in: query name: experimental deprecated: true @@ -235,8 +228,9 @@ paths: default: false /epm/packages/limited: get: - summary: Packages - Get limited list - tags: [] + summary: Get limited package list + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -255,8 +249,9 @@ paths: parameters: [] /epm/packages: get: - summary: Packages - List - tags: [] + summary: List packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -286,7 +281,7 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) - in: query name: experimental deprecated: true @@ -298,8 +293,9 @@ paths: schema: type: string post: - summary: Packages - Install by upload - tags: [] + summary: Install by package by direct upload + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -351,8 +347,9 @@ paths: format: binary /epm/packages/_bulk: post: - summary: Packages - Bulk install - tags: [] + summary: Bulk install packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -371,7 +368,7 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) requestBody: content: application/json: @@ -390,8 +387,9 @@ paths: - packages /epm/packages/{pkgkey}: get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -434,11 +432,12 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) deprecated: true post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -484,8 +483,9 @@ paths: type: boolean deprecated: true delete: - summary: Packages - Delete - tags: [] + summary: Delete ackage + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -531,8 +531,9 @@ paths: deprecated: true /epm/packages/{pkgName}/{pkgVersion}: get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -600,10 +601,11 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -654,8 +656,9 @@ paths: ignore_constraints: type: boolean put: - summary: Packages - Update - tags: [] + summary: Update package settings + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -693,8 +696,9 @@ paths: keepPoliciesUpToDate: type: boolean delete: - summary: Packages - Delete - tags: [] + summary: Delete package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -734,8 +738,9 @@ paths: type: boolean /epm/packages/{pkgName}/{pkgVersion}/{filePath}: get: - summary: Packages - Get file from registry - tags: [] + summary: Get package file + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -771,8 +776,9 @@ paths: required: true /epm/packages/{pkgName}/stats: get: - summary: Get stats for a package - tags: [] + summary: Get package stats + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -798,8 +804,9 @@ paths: required: true /agents/setup: get: - summary: Agents setup - Info - tags: [] + summary: Get agent setup info + tags: + - Agents responses: '200': description: OK @@ -813,7 +820,9 @@ paths: security: - basicAuth: [] post: - summary: Agents setup - Create + summary: Initiate agent setup + tags: + - Agents operationId: setup-agents responses: '200': @@ -841,8 +850,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /agent-status: get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -888,8 +898,9 @@ paths: deprecated: true /agent_status: get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -948,8 +959,9 @@ paths: required: false /agent_status/data: get: - summary: Agents - Get incoming data - tags: [] + summary: Get incoming agent data + tags: + - Agent status responses: '200': description: OK @@ -980,8 +992,9 @@ paths: required: true /agents: get: - summary: Agents - List - tags: [] + summary: List agents + tags: + - Agents responses: '200': description: OK @@ -1010,8 +1023,9 @@ paths: - basicAuth: [] /agents/bulk_upgrade: post: - summary: Agents - Bulk Upgrade - tags: [] + summary: Bulk upgrade agents + tags: + - Agents responses: '200': description: OK @@ -1043,7 +1057,9 @@ paths: start_time: '2022-08-03T14:00:00.000Z' /agents/action_status: get: - summary: Agents - Action status + summary: Get agent action status + tags: + - Agent actions parameters: - $ref: '#/components/parameters/page_size' - $ref: '#/components/parameters/page_index' @@ -1116,8 +1132,9 @@ paths: in: path required: true get: - summary: Agent - Info - tags: [] + summary: Get agent by ID + tags: + - Agents responses: '200': description: OK @@ -1136,8 +1153,9 @@ paths: parameters: - $ref: '#/components/parameters/with_metrics' put: - summary: Agent - Update - tags: [] + summary: Update agent by ID + tags: + - Agents responses: '200': description: OK @@ -1169,8 +1187,9 @@ paths: items: type: string delete: - summary: Agent - Delete - tags: [] + summary: Delete agent by ID + tags: + - Agents responses: '200': description: OK @@ -1198,8 +1217,9 @@ paths: in: path required: true post: - summary: Agent - Actions - tags: [] + summary: Create agent action + tags: + - Agent actions responses: '200': description: OK @@ -1243,8 +1263,9 @@ paths: in: path required: true post: - summary: Agent - Cancel Action - tags: [] + summary: Cancel agent action + tags: + - Agent actions responses: '200': description: OK @@ -1273,8 +1294,9 @@ paths: in: path required: true get: - summary: Get agent upload file - tags: [] + summary: Get file uploaded by agent + tags: + - Agents responses: '200': description: OK @@ -1302,8 +1324,9 @@ paths: in: path required: true post: - summary: Agent - Reassign - tags: [] + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -1328,8 +1351,9 @@ paths: required: - policy_id put: - summary: Agent - Reassign - tags: [] + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -1362,8 +1386,9 @@ paths: in: path required: true post: - summary: Agent - Unenroll - tags: [] + summary: Unenroll agent + tags: + - Agents responses: '200': description: OK @@ -1407,8 +1432,9 @@ paths: in: path required: true post: - summary: Agent - Upgrade - tags: [] + summary: Upgrade agent + tags: + - Agents responses: '200': description: OK @@ -1436,7 +1462,8 @@ paths: required: true get: summary: List agent uploads - tags: [] + tags: + - Agents responses: '200': description: OK @@ -1457,8 +1484,9 @@ paths: operationId: list-agent-uploads /agents/bulk_reassign: post: - summary: Agents - Bulk reassign - tags: [] + summary: Bulk reassign agents + tags: + - Agents responses: '200': description: OK @@ -1499,8 +1527,9 @@ paths: agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' /agents/bulk_unenroll: post: - summary: Agents - Bulk unenroll - tags: [] + summary: Bulk unenroll agents + tags: + - Agents responses: '200': description: OK @@ -1546,8 +1575,9 @@ paths: - agent2 /agents/bulk_update_agent_tags: post: - summary: Agents - Bulk update tags - tags: [] + summary: Bulk update agent tags + tags: + - Agents responses: '200': description: OK @@ -1599,8 +1629,9 @@ paths: - existingTag /agents/tags: get: - summary: Agent Tags - List - description: List all agent tags + summary: List agent tags + tags: + - Agents responses: '200': description: OK @@ -1619,8 +1650,9 @@ paths: in: path required: true post: - summary: Agent - Request Diagnostics - tags: [] + summary: Request agent diagnostics + tags: + - Agents responses: '200': description: OK @@ -1638,8 +1670,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /agents/bulk_request_diagnostics: post: - summary: Agent - Bulk Request Diagnostics - tags: [] + summary: Bulk request diagnostics from agents + tags: + - Agents responses: '200': description: OK @@ -1677,8 +1710,9 @@ paths: agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' /agent_policies: get: - summary: Agent policies - List - tags: [] + summary: List agent policies + tags: + - Agent policies responses: '200': description: OK @@ -1727,8 +1761,9 @@ paths: 0 if set to true. description: '' post: - summary: Agent policy - Create - tags: [] + summary: Create agent policy + tags: + - Agent policies responses: '200': description: OK @@ -1758,8 +1793,9 @@ paths: in: path required: true get: - summary: Agent policy - Info - tags: [] + summary: Get agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -1778,8 +1814,9 @@ paths: description: Get one agent policy parameters: [] put: - summary: Agent policy - Update - tags: [] + summary: Update agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -1810,7 +1847,9 @@ paths: in: path required: true post: - summary: Agent policy - copy one policy + summary: Copy agent policy by ID + tags: + - Agent policies operationId: agent-policy-copy parameters: - $ref: '#/components/parameters/kbn_xsrf' @@ -1843,7 +1882,9 @@ paths: description: '' /agent_policies/{agentPolicyId}/full: get: - summary: Agent policy - Get full policy + summary: Get full agent policy by ID + tags: + - Agent policies operationId: agent-policy-full responses: '200': @@ -1882,7 +1923,9 @@ paths: required: false /agent_policies/{agentPolicyId}/download: get: - summary: Agent policy - Download + summary: Download agent policy by ID + tags: + - Agent policies operationId: agent-policy-download responses: '200': @@ -1919,8 +1962,9 @@ paths: required: false /agent_policies/_bulk_get: post: - summary: Agent policies - Bulk Get - tags: [] + summary: Bulk get agent policies + tags: + - Agent policies requestBody: content: application/json: @@ -1960,7 +2004,9 @@ paths: parameters: [] /agent_policies/delete: post: - summary: Agent policy - Delete + summary: Delete agent policy by ID + tags: + - Agent policies operationId: delete-agent-policy responses: '200': @@ -1994,8 +2040,9 @@ paths: parameters: [] /data_streams: get: - summary: Data streams - List - tags: [] + summary: List data streams + tags: + - Data streams responses: '200': description: OK @@ -2014,8 +2061,9 @@ paths: parameters: [] /enrollment-api-keys: get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -2050,8 +2098,9 @@ paths: parameters: [] deprecated: true post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK @@ -2080,8 +2129,9 @@ paths: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2099,8 +2149,9 @@ paths: operationId: get-enrollment-api-key-deprecated deprecated: true delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2123,8 +2174,9 @@ paths: deprecated: true /enrollment_api_keys: get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -2158,8 +2210,9 @@ paths: operationId: get-enrollment-api-keys parameters: [] post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK @@ -2187,8 +2240,9 @@ paths: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2205,8 +2259,9 @@ paths: $ref: '#/components/responses/error' operationId: get-enrollment-api-key delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2228,8 +2283,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies: get: - summary: Package policies - List - tags: [] + summary: List package policies + tags: + - Package policies responses: '200': description: OK @@ -2257,7 +2313,9 @@ paths: parameters: [] parameters: [] post: - summary: Package policy - Create + summary: Create package policy + tags: + - Package policies operationId: create-package-policy responses: '200': @@ -2285,8 +2343,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies/_bulk_get: post: - summary: Package policies - Bulk Get - tags: [] + summary: Bulk get package policies + tags: + - Package policies requestBody: content: application/json: @@ -2323,7 +2382,9 @@ paths: parameters: [] /package_policies/delete: post: - summary: Package policy - Delete + summary: Delete package policy + tags: + - Package policies operationId: post-delete-package-policy requestBody: content: @@ -2364,7 +2425,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies/upgrade: post: - summary: Package policy - Upgrade + summary: Upgrade package policy to a newer package version + tags: + - Package policies operationId: upgrade-package-policy requestBody: content: @@ -2401,7 +2464,9 @@ paths: $ref: '#/components/responses/error' /package_policies/upgrade/dryrun: post: - summary: Package policy - Upgrade Dry run + summary: Dry run package policy upgrade + tags: + - Package policies operationId: upgrade-package-policy-dry-run requestBody: content: @@ -2439,8 +2504,9 @@ paths: $ref: '#/components/responses/error' /package_policies/{packagePolicyId}: get: - summary: Package policy - Info - tags: [] + summary: Get package policy by ID + tags: + - Package policies responses: '200': description: OK @@ -2463,7 +2529,9 @@ paths: in: path required: true put: - summary: Package policy - Update + summary: Update package policy by ID + tags: + - Package policies operationId: update-package-policy requestBody: content: @@ -2490,8 +2558,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' delete: - summary: Package policy - Delete - tags: [] + summary: Delete package policy by ID + tags: + - Package policies operationId: delete-package-policy responses: '200': @@ -2514,8 +2583,9 @@ paths: in: query /outputs: get: - summary: Outputs - tags: [] + summary: List outputs + tags: + - Outputs responses: '200': description: OK @@ -2538,9 +2608,9 @@ paths: $ref: '#/components/responses/error' operationId: get-outputs post: - summary: Outputs - description: Create a new output - tags: [] + summary: Create output + tags: + - Outputs responses: '200': description: OK @@ -2585,8 +2655,9 @@ paths: operationId: post-outputs /outputs/{outputId}: get: - summary: Output - Info - tags: [] + summary: Get output by ID + tags: + - Outputs responses: '200': description: OK @@ -2609,7 +2680,9 @@ paths: in: path required: true delete: - summary: Output - Delete + summary: Delete output by ID + tags: + - Outputs operationId: delete-output responses: '200': @@ -2628,7 +2701,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Output - Update + summary: Update output by ID + tags: + - Outputs operationId: update-output requestBody: content: @@ -2675,10 +2750,31 @@ paths: $ref: '#/components/responses/error' parameters: - $ref: '#/components/parameters/kbn_xsrf' + /logstash_api_keys: + post: + summary: Generate Logstash API key + tags: + - Outputs + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + api_key: + type: string + '400': + $ref: '#/components/responses/error' + operationId: generate-logstash-api-key + parameters: + - $ref: '#/components/parameters/kbn_xsrf' /agent_download_sources: get: - summary: Agent Download Sources - tags: [] + summary: List agent binary download sources + tags: + - Agent binary download sources responses: '200': description: OK @@ -2701,9 +2797,9 @@ paths: $ref: '#/components/responses/error' operationId: get-download-sources post: - summary: Agent Download Sources - description: Create a new agent download source - tags: [] + summary: Create agent binary download source + tags: + - Agent binary download sources responses: '200': description: OK @@ -2737,8 +2833,9 @@ paths: operationId: post-download-sources /agent_download_sources/{sourceId}: get: - summary: Agent Download Sources - Info - tags: [] + summary: Get agent binary download source by ID + tags: + - Agent binary download sources responses: '200': description: OK @@ -2761,7 +2858,9 @@ paths: in: path required: true delete: - summary: Agent Download Sources - Delete + summary: Delete agent binary download source by ID + tags: + - Agent binary download sources operationId: delete-download-source responses: '200': @@ -2780,7 +2879,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Agent Download Sources - Update + summary: Update agent binary download source by ID + tags: + - Agent binary download sources operationId: update-download-source requestBody: content: @@ -2814,30 +2915,11 @@ paths: $ref: '#/components/responses/error' parameters: - $ref: '#/components/parameters/kbn_xsrf' - /logstash_api_keys: - post: - summary: Generate Logstash API key - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - api_key: - type: string - '400': - $ref: '#/components/responses/error' - operationId: generate-logstash-api-key - parameters: - - $ref: '#/components/parameters/kbn_xsrf' /fleet_server_hosts: get: - summary: Fleet Server Hosts - List - description: Return a list of Fleet server hosts - tags: [] + summary: List Fleet Server hosts + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2860,9 +2942,9 @@ paths: $ref: '#/components/responses/error' operationId: get-fleet-server-hosts post: - summary: Fleet Server Hosts - Create - description: Create a new Fleet Server Host - tags: [] + summary: Create Fleet Server host + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2897,8 +2979,9 @@ paths: operationId: post-fleet-server-hosts /fleet_server_hosts/{itemId}: get: - summary: Fleet Server Hosts - Info - tags: [] + summary: Get Fleet Server host by ID + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2921,7 +3004,9 @@ paths: in: path required: true delete: - summary: Fleet Server Hosts - Delete + summary: Delete Fleet Server host by ID + tags: + - Fleet Server hosts operationId: delete-fleet-server-hosts responses: '200': @@ -2940,7 +3025,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Fleet Server Hosts - Update + summary: Update Fleet Server host by ID + tags: + - Fleet Server hosts operationId: update-fleet-server-hosts requestBody: content: @@ -2974,9 +3061,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /proxies: get: - summary: Fleet Proxies - List - description: Return a list of Proxies - tags: [] + summary: List proxies + tags: + - Proxies responses: '200': description: OK @@ -2999,9 +3086,9 @@ paths: $ref: '#/components/responses/error' operationId: get-fleet-proxies post: - summary: Fleet Proxies - Create - description: Create a new Fleet Server Host - tags: [] + summary: Create proxy + tags: + - Proxies responses: '200': description: OK @@ -3040,8 +3127,9 @@ paths: operationId: post-fleet-proxies /proxies/{itemId}: get: - summary: Fleet Proxies - Info - tags: [] + summary: Get proxy by ID + tags: + - Proxies responses: '200': description: OK @@ -3064,7 +3152,9 @@ paths: in: path required: true delete: - summary: Fleet Proxies - Delete + summary: Delete proxy by ID + tags: + - Proxies operationId: delete-fleet-proxies responses: '200': @@ -3083,7 +3173,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Fleet Proxies - Update + summary: Update proxy by ID + tags: + - Proxies operationId: update-fleet-proxies requestBody: content: @@ -3121,8 +3213,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /kubernetes: get: - summary: Get K8s Full Agent Manifest - tags: [] + summary: Get full K8s agent manifest + tags: + - Kubernetes responses: '200': description: OK @@ -3230,6 +3323,20 @@ components: required: false schema: type: boolean + responses: + error: + description: Generic Error + content: + application/json: + schema: + type: object + properties: + statusCode: + type: number + error: + type: string + message: + type: string schemas: fleet_setup_response: title: Fleet Setup response @@ -4612,19 +4719,5 @@ components: required: - name - url - responses: - error: - description: Generic Error - content: - application/json: - schema: - type: object - properties: - statusCode: - type: number - error: - type: string - message: - type: string security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 37c33c28b1053..ca9d1cd3c8e19 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -13,18 +13,18 @@ servers: - url: 'http://localhost:5601/api/fleet' description: local paths: - # plugin-wide endpoint(s) + # Fleet internals + /health_check: + $ref: paths/health_check.yaml /setup: $ref: paths/setup.yaml /settings: $ref: paths/settings.yaml - # App endpoints - /health_check: - $ref: paths/health_check.yaml /service-tokens: $ref: paths/service_tokens_deprecated.yaml /service_tokens: $ref: paths/service_tokens.yaml + # EPM / integrations endpoints /epm/verification_key_id: $ref: paths/epm@verification_key_id.yaml @@ -44,7 +44,8 @@ paths: $ref: paths/epm@get_file.yaml '/epm/packages/{pkgName}/stats': $ref: 'paths/epm@packages@{pkg_name}@stats.yaml' - # Agent-related endpoints + + # Agent endpoints /agents/setup: $ref: paths/agents@setup.yaml /agent-status: @@ -87,6 +88,7 @@ paths: $ref: 'paths/agents@{agent_id}@request_diagnostics.yaml' /agents/bulk_request_diagnostics: $ref: 'paths/agents@bulk_request_diagnostics.yaml' + # Agent policies endpoints /agent_policies: $ref: paths/agent_policies.yaml @@ -102,9 +104,11 @@ paths: $ref: paths/agent_policies@_bulk_get.yaml /agent_policies/delete: $ref: paths/agent_policies@delete.yaml + # Data streams endpoints /data_streams: $ref: paths/data_streams.yaml + # Enrollment endpoints /enrollment-api-keys: $ref: paths/enrollment_api_keys_deprecated.yaml @@ -114,6 +118,7 @@ paths: $ref: paths/enrollment_api_keys.yaml '/enrollment_api_keys/{keyId}': $ref: 'paths/enrollment_api_keys@{key_id}.yaml' + # Package policies endpoints /package_policies: $ref: paths/package_policies.yaml @@ -127,27 +132,33 @@ paths: $ref: paths/package_policies@upgrade_dryrun.yaml '/package_policies/{packagePolicyId}': $ref: 'paths/package_policies@{package_policy_id}.yaml' + # Outputs /outputs: $ref: paths/outputs.yaml /outputs/{outputId}: $ref: paths/outputs@{output_id}.yaml + /logstash_api_keys: + $ref: paths/logstash_api_keys.yaml + + # Agent binary download sources /agent_download_sources: $ref: paths/agent_download_sources.yaml /agent_download_sources/{sourceId}: $ref: paths/agent_download_sources@{source_id}.yaml - /logstash_api_keys: - $ref: paths/logstash_api_keys.yaml + # Fleet server hosts /fleet_server_hosts: $ref: paths/fleet_server_hosts.yaml /fleet_server_hosts/{itemId}: $ref: paths/fleet_server_hosts@{item_id}.yaml + # Fleet proxies /proxies: $ref: paths/proxies.yaml /proxies/{itemId}: $ref: paths/proxies@{item_id}.yaml + # K8s /kubernetes: $ref: paths/kubernetes.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml index 619b3c626ac66..89a69c9adfdaf 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Download Sources - tags: [] + summary: List agent binary download sources + tags: + - Agent binary download sources responses: '200': description: OK @@ -23,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-download-sources post: - summary: Agent Download Sources - description: 'Create a new agent download source' - tags: [] + summary: Create agent binary download source + tags: + - Agent binary download sources responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml index 364d292082c8b..afb7771283e59 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Download Sources - Info - tags: [] + summary: Get agent binary download source by ID + tags: + - Agent binary download sources responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Agent Download Sources - Delete + summary: Delete agent binary download source by ID + tags: + - Agent binary download sources operationId: delete-download-source responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Agent Download Sources - Update + summary: Update agent binary download source by ID + tags: + - Agent binary download sources operationId: update-download-source requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml index 33fd8a2348412..cbf29f3859519 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml @@ -1,6 +1,7 @@ get: - summary: Agent policies - List - tags: [] + summary: List agent policies + tags: + - Agent policies responses: '200': description: OK @@ -44,8 +45,9 @@ get: description: '' post: - summary: Agent policy - Create - tags: [] + summary: Create agent policy + tags: + - Agent policies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml index 75267e2a262a9..ace09ef721677 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml @@ -1,6 +1,7 @@ post: - summary: Agent policies - Bulk Get - tags: [] + summary: Bulk get agent policies + tags: + - Agent policies requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml index 51967a697cf0e..966d8abc1e328 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml @@ -1,5 +1,7 @@ post: - summary: Agent policy - Delete + summary: Delete agent policy by ID + tags: + - Agent policies operationId: delete-agent-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml index 4a7e88abcbab8..55d644ab0aab2 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Agent policy - Info - tags: [] + summary: Get agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -25,8 +26,9 @@ get: description: Get one agent policy parameters: [] put: - summary: Agent policy - Update - tags: [] + summary: Update agent policy by ID + tags: + - Agent policies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml index 21d4a1d493b01..dab79eef58dff 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml @@ -5,7 +5,9 @@ parameters: in: path required: true post: - summary: Agent policy - copy one policy + summary: Copy agent policy by ID + tags: + - Agent policies operationId: agent-policy-copy parameters: - $ref: ../components/headers/kbn_xsrf.yaml @@ -36,4 +38,3 @@ post: required: - name description: '' - diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml index 5c7887d6f1bb2..1748950fdaf09 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml @@ -1,6 +1,7 @@ - get: - summary: Agent policy - Download + summary: Download agent policy by ID + tags: + - Agent policies operationId: agent-policy-download responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml index 1a79266e27732..dc5a1b996b2e4 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml @@ -1,6 +1,7 @@ - get: - summary: Agent policy - Get full policy + summary: Get full agent policy by ID + tags: + - Agent policies operationId: agent-policy-full responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml index 46d8ac2f32ff9..6d4b1c77d991a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -29,7 +30,7 @@ get: updating: type: integer all: - type: integer + type: integer active: type: integer required: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml index a16fa2f71f8a8..7e90097c3b4dd 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Get incoming data - tags: [] + summary: Get incoming agent data + tags: + - Agent status responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml index 874cd38632ed3..fe44311fa9801 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml index f01584ef4665a..85a6f6c7ab30a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Tags - List - description: List all agent tags + summary: List agent tags + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml index b9b62f23552e1..cd74ff0a56636 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - List - tags: [] + summary: List agents + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml index 50494a29f4096..1c2d013457d6f 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml @@ -1,8 +1,10 @@ get: - summary: Agents - Action status + summary: Get agent action status + tags: + - Agent actions parameters: - - $ref: ../components/parameters/page_size.yaml - - $ref: ../components/parameters/page_index.yaml + - $ref: ../components/parameters/page_size.yaml + - $ref: ../components/parameters/page_index.yaml responses: '200': description: OK @@ -33,7 +35,7 @@ get: nbAgentsAck: type: number nbAgentsFailed: - type: number + type: number version: type: string startTime: @@ -49,7 +51,7 @@ get: newPolicyId: type: string creationTime: - type: string + type: string required: - actionId - complete @@ -63,4 +65,4 @@ get: - items '400': $ref: ../components/responses/error.yaml - operationId: agents-action-status \ No newline at end of file + operationId: agents-action-status diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml index 625aee38eafc4..b93b2bd6b9a08 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk reassign - tags: [] + summary: Bulk reassign agents + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-reassign-agents @@ -38,4 +39,4 @@ post: - agents example: policy_id: policy_id - agents: "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" + agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml index e2952e7ae51e0..3f0733ed8f258 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml @@ -1,6 +1,7 @@ post: - summary: Agent - Bulk Request Diagnostics - tags: [] + summary: Bulk request diagnostics from agents + tags: + - Agents responses: '200': description: OK @@ -35,4 +36,4 @@ post: required: - agents example: - agents: "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" + agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml index 7527558a4cc10..1ab9e4038b978 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk unenroll - tags: [] + summary: Bulk unenroll agents + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-unenroll-agents diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml index 77c0c0d4cfa7c..ff4c6597b6be0 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk update tags - tags: [] + summary: Bulk update agent tags + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-update-agent-tags @@ -39,10 +40,10 @@ post: items: type: string batchSize: - type: number + type: number required: - agents example: agents: [agent1, agent2] tagsToAdd: [newTag] - tagsToRemove: [existingTag] + tagsToRemove: [existingTag] diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml index b8863eaf271fd..ccb55c7c62b17 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk Upgrade - tags: [] + summary: Bulk upgrade agents + tags: + - Agents responses: '200': description: OK content: application/json: schema: - type: object - properties: - actionId: - type: string + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-upgrade-agents diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml index 162c9e4cb5bc3..36ae723527f9b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Current Bulk Upgrades - tags: [] + summary: List current bulk upgrade operations + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml index 82192ade7856b..15f6dd8a421d1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml @@ -10,8 +10,9 @@ parameters: in: path required: true get: - summary: Get agent upload file - tags: [] + summary: Get file uploaded by agent + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml index 104ae1ba084da..214f3a8e68240 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml @@ -1,6 +1,7 @@ get: - summary: Agents setup - Info - tags: [] + summary: Get agent setup info + tags: + - Agents responses: '200': description: OK @@ -14,7 +15,9 @@ get: security: - basicAuth: [] post: - summary: Agents setup - Create + summary: Initiate agent setup + tags: + - Agents operationId: setup-agents responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml index 7bf3a7d73f31b..93242e5912a17 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Agent - Info - tags: [] + summary: Get agent by ID + tags: + - Agents responses: '200': description: OK @@ -25,8 +26,9 @@ get: parameters: - $ref: ../components/parameters/with_metrics.yaml put: - summary: Agent - Update - tags: [] + summary: Update agent by ID + tags: + - Agents responses: '200': description: OK @@ -58,8 +60,9 @@ put: items: type: string delete: - summary: Agent - Delete - tags: [] + summary: Delete agent by ID + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml index 38cce1ea54db3..cd327e453b9a7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Actions - tags: [] + summary: Create agent action + tags: + - Agent actions responses: '200': description: OK @@ -35,5 +36,5 @@ post: schema: type: object properties: - action: - $ref: ../components/schemas/agent_action.yaml + action: + $ref: ../components/schemas/agent_action.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml index c9bf661d88a85..f91acd133355d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml @@ -10,8 +10,9 @@ parameters: in: path required: true post: - summary: Agent - Cancel Action - tags: [] + summary: Cancel agent action + tags: + - Agent actions responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml index af00d1563854e..c210cee12d424 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Reassign - tags: [] + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -31,8 +32,9 @@ post: required: - policy_id put: - summary: Agent - Reassign - tags: [] + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -57,4 +59,3 @@ put: required: - policy_id deprecated: true - diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml index 13b335ffe2300..37aed12d5f4f1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Request Diagnostics - tags: [] + summary: Request agent diagnostics + tags: + - Agents responses: '200': description: OK @@ -22,4 +23,3 @@ post: operationId: request-diagnostics-agent parameters: - $ref: ../components/headers/kbn_xsrf.yaml - diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml index b9664ae650112..c30bebfad328a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml @@ -5,30 +5,31 @@ parameters: in: path required: true post: - summary: Agent - Unenroll - tags: [] + summary: Unenroll agent + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object + description: OK + content: + application/json: + schema: + type: object '400': - description: BAD REQUEST - content: - application/json: - schema: - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - enum: - - 400 + description: BAD REQUEST + content: + application/json: + schema: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + enum: + - 400 operationId: unenroll-agent parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml index c6d66f6f52386..d824d4a54f985 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Upgrade - tags: [] + summary: Upgrade agent + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml index de812b0e363e3..f92acc7fe5086 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml @@ -6,7 +6,8 @@ parameters: required: true get: summary: List agent uploads - tags: [] + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml b/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml index c9e9f1be897aa..bb8c667ba933e 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml @@ -1,6 +1,7 @@ get: - summary: Data streams - List - tags: [] + summary: List data streams + tags: + - Data streams responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml index 5bc257606087f..3351b63026e57 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml @@ -1,6 +1,7 @@ get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -34,8 +35,9 @@ get: operationId: get-enrollment-api-keys parameters: [] post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml index bdff9a7152e45..d64b1053f0dc4 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -23,8 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-enrollment-api-key delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml index 36c0f7f3ef01f..c0f5be7521e8a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -24,8 +25,9 @@ get: operationId: get-enrollment-api-key-deprecated deprecated: true delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml index c5e378c563afc..19022a0b08223 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -35,8 +36,9 @@ get: parameters: [] deprecated: true post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml index a97c06f66c629..e733f780abe04 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml @@ -1,6 +1,7 @@ get: - summary: Package categories - tags: [] + summary: List package categories + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,21 +13,21 @@ get: $ref: ../components/responses/error.yaml operationId: get-package-categories parameters: - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to include prerelease packages in categories count (e.g. beta, rc, preview) - - in: query + Whether to include prerelease packages in categories count (e.g. beta, rc, preview) + - in: query name: experimental deprecated: true schema: type: boolean default: false - - in: query + - in: query name: include_policy_templates schema: type: boolean - default: false \ No newline at end of file + default: false diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml index 9c194ea6a8e97..b85b72938feda 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Get file from registry - tags: [] + summary: Get package file + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml index ec44a20b61307..a54c45782d3ae 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Get limited list - tags: [] + summary: Get limited package list + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml index 43819dff6d10e..7434fd2d324a5 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - List - tags: [] + summary: List packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -22,26 +23,27 @@ get: caching for the response via `cache-control` headers. If you don't need up-to-date installation info for a package, and are querying for a list of available packages, providing this flag can improve performance substantially. - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) - - in: query + Whether to return prerelease versions of packages (e.g. beta, rc, preview) + - in: query name: experimental deprecated: true schema: type: boolean default: false - - in: query + - in: query name: category schema: type: string post: - summary: Packages - Install by upload - tags: [] + summary: Install by package by direct upload + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -90,4 +92,4 @@ post: application/gzip: schema: type: string - format: binary \ No newline at end of file + format: binary diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml index b27b4ef6729dc..f90a275cd19b6 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml @@ -1,6 +1,7 @@ get: - summary: Get stats for a package - tags: [] + summary: Get package stats + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml index 4cc2e55e9d29e..4fff7f0b21504 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -59,16 +60,17 @@ parameters: name: full description: 'Return all fields from the package manifest, not just those supported by the Elastic Package Registry' in: query - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -119,8 +121,9 @@ post: ignore_constraints: type: boolean put: - summary: Packages - Update - tags: [] + summary: Update package settings + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -158,8 +161,9 @@ put: keepPoliciesUpToDate: type: boolean delete: - summary: Packages - Delete - tags: [] + summary: Delete package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml index 2967238a467a2..035809782bc07 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -36,17 +37,18 @@ get: name: pkgkey in: path required: true - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) deprecated: true post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -92,8 +94,9 @@ post: type: boolean deprecated: true delete: - summary: Packages - Delete - tags: [] + summary: Delete ackage + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml index 7ede68f6b545f..a3775e69a7131 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml @@ -1,6 +1,7 @@ post: - summary: Packages - Bulk install - tags: [] + summary: Bulk install packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,13 +13,13 @@ post: $ref: ../components/responses/error.yaml operationId: bulk-install-packages parameters: - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) requestBody: content: application/json: @@ -32,7 +33,6 @@ post: description: list of package names to install force: type: boolean - description: force install to ignore package verification errors + description: force install to ignore package verification errors required: - packages - diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml index c9216b85cd78f..24de03ab52cd8 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml @@ -1,6 +1,7 @@ get: summary: Get package signature verification key ID - tags: [] + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,7 +13,7 @@ get: body: type: object properties: - id: + id: type: string nullable: true description: the key ID of the GPG key used to verify package signatures @@ -23,19 +24,4 @@ get: '400': $ref: ../components/responses/error.yaml operationId: packages-get-verification-key-id -parameters: - - schema: - type: string - name: pkgName - in: path - required: true - - schema: - type: string - name: pkgVersion - in: path - required: true - - schema: - type: string - name: filePath - in: path - required: true +parameters: [] diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml index da599dddca1e3..d7668f3683b7b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml @@ -1,7 +1,7 @@ get: - summary: Fleet Server Hosts - List - description: Return a list of Fleet server hosts - tags: [] + summary: List Fleet Server hosts + tags: + - Fleet Server hosts responses: '200': description: OK @@ -24,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-fleet-server-hosts post: - summary: Fleet Server Hosts - Create - description: 'Create a new Fleet Server Host' - tags: [] + summary: Create Fleet Server host + tags: + - Fleet Server hosts responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml index 968f81d8c6181..d46a8b86fb7f6 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Fleet Server Hosts - Info - tags: [] + summary: Get Fleet Server host by ID + tags: + - Fleet Server hosts responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Fleet Server Hosts - Delete + summary: Delete Fleet Server host by ID + tags: + - Fleet Server hosts operationId: delete-fleet-server-hosts responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Fleet Server Hosts - Update + summary: Update Fleet Server host by ID + tags: + - Fleet Server hosts operationId: update-fleet-server-hosts requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml index 84283ca80dbf0..ae87da3ff0e52 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml @@ -1,6 +1,7 @@ post: - summary: Fleet Server Health Check - tags: [] + summary: Fleet Server health check + tags: + - Fleet internals responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml b/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml index d7852db70fccd..41110808cd62d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml @@ -1,6 +1,7 @@ get: - summary: Get K8s Full Agent Manifest - tags: [] + summary: Get full K8s agent manifest + tags: + - Kubernetes responses: '200': description: OK @@ -15,18 +16,18 @@ get: $ref: ../components/responses/error.yaml operationId: get-full-k8s-manifest parameters: - - schema: - type: boolean - name: download - in: query - required: false - - schema: - type: string - name: fleetServer - in: query - required: false - - schema: - type: string - name: enrolToken - in: query - required: false + - schema: + type: boolean + name: download + in: query + required: false + - schema: + type: string + name: fleetServer + in: query + required: false + - schema: + type: string + name: enrolToken + in: query + required: false diff --git a/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml index 495d792191798..74e3bdde4cac8 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml @@ -1,6 +1,7 @@ post: summary: Generate Logstash API key - tags: [] + tags: + - Outputs responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml b/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml index 335d8ec570ca1..5ba06a5c36372 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml @@ -1,6 +1,7 @@ get: - summary: Outputs - tags: [] + summary: List outputs + tags: + - Outputs responses: '200': description: OK @@ -23,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-outputs post: - summary: Outputs - description: 'Create a new output' - tags: [] + summary: Create output + tags: + - Outputs responses: '200': description: OK @@ -50,7 +51,7 @@ post: type: string type: type: string - enum: ["elasticsearch"] + enum: ['elasticsearch'] is_default: type: boolean is_default_monitoring: diff --git a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml index ca01024288b95..8bd6ae2fc288c 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Output - Info - tags: [] + summary: Get output by ID + tags: + - Outputs responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Output - Delete + summary: Delete output by ID + tags: + - Outputs operationId: delete-output responses: '200': @@ -33,7 +36,7 @@ delete: schema: type: object properties: - id: + id: type: string required: - id @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Output - Update + summary: Update output by ID + tags: + - Outputs operationId: update-output requestBody: content: @@ -54,14 +59,14 @@ put: type: string type: type: string - enum: ["elasticsearch"] + enum: ['elasticsearch'] is_default: type: boolean is_default_monitoring: type: boolean hosts: type: array - items: + items: type: string ca_sha256: type: string diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml index 8959339426d05..0fe987e1727de 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml @@ -1,6 +1,7 @@ get: - summary: Package policies - List - tags: [] + summary: List package policies + tags: + - Package policies responses: '200': description: OK @@ -28,7 +29,9 @@ get: parameters: [] parameters: [] post: - summary: Package policy - Create + summary: Create package policy + tags: + - Package policies operationId: create-package-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml index 1ff515dc2de6a..eb22c7d997575 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml @@ -1,6 +1,7 @@ post: - summary: Package policies - Bulk Get - tags: [] + summary: Bulk get package policies + tags: + - Package policies requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml index 6061267b4b2b8..f21111c23757a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Delete + summary: Delete package policy + tags: + - Package policies operationId: post-delete-package-policy requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml index 09f2727ad678c..2b6e69d49c44e 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Upgrade + summary: Upgrade package policy to a newer package version + tags: + - Package policies operationId: upgrade-package-policy requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml index 6f51bd6812d97..5019aba15898d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Upgrade Dry run + summary: Dry run package policy upgrade + tags: + - Package policies operationId: upgrade-package-policy-dry-run requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml index 9e05e9516d603..30d89d271a5ff 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Package policy - Info - tags: [] + summary: Get package policy by ID + tags: + - Package policies responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true put: - summary: Package policy - Update + summary: Update package policy by ID + tags: + - Package policies operationId: update-package-policy requestBody: content: @@ -50,8 +53,9 @@ put: parameters: - $ref: ../components/headers/kbn_xsrf.yaml delete: - summary: Package policy - Delete - tags: [] + summary: Delete package policy by ID + tags: + - Package policies operationId: delete-package-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml b/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml index a5f59ec0e7cb5..6c2844a9ac3ef 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml @@ -1,7 +1,7 @@ get: - summary: Fleet Proxies - List - description: Return a list of Proxies - tags: [] + summary: List proxies + tags: + - Proxies responses: '200': description: OK @@ -24,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-fleet-proxies post: - summary: Fleet Proxies - Create - description: 'Create a new Fleet Server Host' - tags: [] + summary: Create proxy + tags: + - Proxies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml index 96a3665718753..3a0a10cb35662 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Fleet Proxies - Info - tags: [] + summary: Get proxy by ID + tags: + - Proxies responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Fleet Proxies - Delete + summary: Delete proxy by ID + tags: + - Proxies operationId: delete-fleet-proxies responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Fleet Proxies - Update + summary: Update proxy by ID + tags: + - Proxies operationId: update-fleet-proxies requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml b/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml index c57614c6c5def..e76f18c5b57d7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml @@ -1,6 +1,7 @@ post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml index f081f207b4d1e..73069830be9e1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml @@ -1,6 +1,7 @@ post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/settings.yaml b/x-pack/plugins/fleet/common/openapi/paths/settings.yaml index dc711bcefbfae..4e3d1b3af4bb7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/settings.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/settings.yaml @@ -1,6 +1,7 @@ get: - summary: Settings - tags: [] + summary: Get settings + tags: + - Fleet internals responses: '200': description: OK @@ -12,8 +13,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-settings put: - summary: Settings - Update - tags: [] + summary: Update settings + tags: + - Fleet internals requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml index 048e5cc51faf9..1f1a3cd035665 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml @@ -1,6 +1,7 @@ post: - summary: Setup - tags: [] + summary: Initiate Fleet setup + tags: + - Fleet internals responses: '200': description: OK diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts index 51a516e68ad6d..5947401917245 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts @@ -85,4 +85,13 @@ describe('retryTransientErrors', () => { await expect(retryTransientEsErrors(esCallMock)).rejects.toThrow(error); expect(esCallMock).toHaveBeenCalledTimes(1); }); + + it('retries with additionalResponseStatuses', async () => { + const error = new EsErrors.ResponseError({ statusCode: 123, meta: {} as any, warnings: [] }); + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock, { additionalResponseStatuses: [123] })).toEqual( + 'success' + ); + expect(esCallMock).toHaveBeenCalledTimes(2); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts index c8ea36a4addec..773ed7273d885 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts @@ -17,11 +17,12 @@ const retryResponseStatuses = [ 410, // Gone ]; -const isRetryableError = (e: any) => +const isRetryableError = (e: any, additionalResponseStatuses: number[] = []) => e instanceof EsErrors.NoLivingConnectionsError || e instanceof EsErrors.ConnectionError || e instanceof EsErrors.TimeoutError || - (e instanceof EsErrors.ResponseError && retryResponseStatuses.includes(e?.statusCode!)); + (e instanceof EsErrors.ResponseError && + [...retryResponseStatuses, ...additionalResponseStatuses].includes(e?.statusCode!)); /** * Retries any transient network or configuration issues encountered from Elasticsearch with an exponential backoff. @@ -29,12 +30,16 @@ const isRetryableError = (e: any) => */ export const retryTransientEsErrors = async ( esCall: () => Promise, - { logger, attempt = 0 }: { logger?: Logger; attempt?: number } = {} + { + logger, + attempt = 0, + additionalResponseStatuses = [], + }: { logger?: Logger; attempt?: number; additionalResponseStatuses?: number[] } = {} ): Promise => { try { return await esCall(); } catch (e) { - if (attempt < MAX_ATTEMPTS && isRetryableError(e)) { + if (attempt < MAX_ATTEMPTS && isRetryableError(e, additionalResponseStatuses)) { const retryCount = attempt + 1; const retryDelaySec = Math.min(Math.pow(2, retryCount), 64); // 2s, 4s, 8s, 16s, 32s, 64s, 64s, 64s ... 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 e8881bb247e12..dc775d6f52e01 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 @@ -703,9 +703,13 @@ async function handleTransformInstall({ // start transform by default if not set in yml file // else, respect the setting if (startTransform === undefined || startTransform === true) { - await esClient.transform.startTransform( - { transform_id: transform.installationName }, - { ignore: [409] } + await retryTransientEsErrors( + () => + esClient.transform.startTransform( + { transform_id: transform.installationName }, + { ignore: [409] } + ), + { logger, additionalResponseStatuses: [400] } ); logger.debug(`Started transform: ${transform.installationName}`); } diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts index 806f295ec2c4e..69e1217b0493c 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts @@ -12,6 +12,7 @@ const createClientMock = (): jest.Mocked => ({ ensureInstalledPackage: jest.fn(), fetchFindLatestPackage: jest.fn(), getPackage: jest.fn(), + getPackages: jest.fn(), reinstallEsAssets: jest.fn(), }); 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 dfc02c4f68c57..b0dd6e9dc38b4 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -14,7 +14,10 @@ import type { Logger, } from '@kbn/core/server'; +import type { PackageList } from '../../../common'; + import type { + CategoryId, EsAssetReference, InstallablePackage, Installation, @@ -28,7 +31,7 @@ import { FleetUnauthorizedError } from '../../errors'; import { installTransforms, isTransform } from './elasticsearch/transform/install'; import type { FetchFindLatestPackageOptions } from './registry'; import { fetchFindLatestPackageOrThrow, getPackage } from './registry'; -import { ensureInstalledPackage, getInstallation } from './packages'; +import { ensureInstalledPackage, getInstallation, getPackages } from './packages'; export type InstalledAssetType = EsAssetReference; @@ -56,6 +59,12 @@ export interface PackageClient { packageVersion: string ): Promise<{ packageInfo: ArchivePackage; paths: string[] }>; + getPackages(params?: { + excludeInstallStatus?: false; + category?: CategoryId; + prerelease?: false; + }): Promise; + reinstallEsAssets( packageInfo: InstallablePackage, assetPaths: string[] @@ -137,6 +146,21 @@ class PackageClientImpl implements PackageClient { return getPackage(packageName, packageVersion, options); } + public async getPackages(params?: { + excludeInstallStatus?: false; + category?: CategoryId; + prerelease?: false; + }) { + const { excludeInstallStatus, category, prerelease } = params || {}; + await this.#runPreflight(); + return getPackages({ + savedObjectsClient: this.internalSoClient, + excludeInstallStatus, + category, + prerelease, + }); + } + public async reinstallEsAssets( packageInfo: InstallablePackage, assetPaths: string[] diff --git a/x-pack/plugins/graph/kibana.jsonc b/x-pack/plugins/graph/kibana.jsonc index c47a12d71cc92..5c85742b492a2 100644 --- a/x-pack/plugins/graph/kibana.jsonc +++ b/x-pack/plugins/graph/kibana.jsonc @@ -16,7 +16,9 @@ "navigation", "savedObjects", "unifiedSearch", - "inspector" + "inspector", + "savedObjectsManagement", + "savedObjectsFinder", ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index bbb14a96ac5eb..90cbc2b88b19f 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -35,6 +35,7 @@ import('./font_awesome'); import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; import { SpacesApi } from '@kbn/spaces-plugin/public'; import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { GraphSavePolicy } from './types'; import { graphRouter } from './router'; import { checkLicense } from '../common/check_license'; @@ -72,6 +73,7 @@ export interface GraphDependencies { history: ScopedHistory; spaces?: SpacesApi; inspect: InspectorPublicPluginStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export type GraphServices = Omit; diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index 75aafeb1a7868..8fcff177d054b 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -43,6 +43,7 @@ export const WorkspaceRoute = ({ spaces, indexPatterns: getIndexPatternProvider, inspect, + savedObjectsManagement, }, }: WorkspaceRouteProps) => { /** @@ -70,9 +71,10 @@ export const WorkspaceRoute = ({ storage, data, unifiedSearch, + savedObjectsManagement, ...coreStart, }), - [coreStart, data, storage, unifiedSearch] + [coreStart, data, storage, unifiedSearch, savedObjectsManagement] ); const { loading, requestAdapter, callNodeProxy, callSearchNodeProxy, handleSearchQueryError } = diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 81be2e191516a..6b6c06dbc02ba 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -77,7 +77,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { const kibana = useKibana(); const { services, overlays } = kibana; - const { http, uiSettings, application, data } = services; + const { http, uiSettings, application, data, savedObjectsManagement } = services; const [hasDataViews, setHasDataViews] = useState(true); useEffect(() => { @@ -90,7 +90,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { if (!overlays || !application) return null; const onOpenDatasourcePicker = () => { - openSourceModal({ overlays, http, uiSettings }, onIndexPatternSelected); + openSourceModal({ overlays, http, uiSettings, savedObjectsManagement }, onIndexPatternSelected); }; let content = ( diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index ca21e16c0fb36..e1ee8cb9d6331 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -29,6 +29,7 @@ import { GraphStore, setDatasource, submitSearchSaga } from '../state_management import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); @@ -42,6 +43,7 @@ function getServiceMocks() { }, } as IUiSettingsClient, savedObjects: {} as SavedObjectsStart, + savedObjectsManagement: {} as SavedObjectsManagementPluginStart, notifications: {} as NotificationsStart, docLinks: { links: { diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index fcd5d576116d9..5bf23c1705dec 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -107,9 +107,9 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) notifications, http, docLinks, + savedObjectsManagement, } = services; if (!overlays) return null; - return (
    { @@ -131,7 +131,11 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) data-test-subj="graphDatasourceButton" onClick={() => { confirmWipeWorkspace( - () => openSourceModal({ overlays, http, uiSettings }, onIndexPatternSelected), + () => + openSourceModal( + { overlays, http, uiSettings, savedObjectsManagement }, + onIndexPatternSelected + ), i18n.translate('xpack.graph.clearWorkspace.confirmText', { defaultMessage: 'If you change data sources, your current fields and vertices will be reset.', diff --git a/x-pack/plugins/graph/public/components/source_picker.tsx b/x-pack/plugins/graph/public/components/source_picker.tsx index ceb165fd50840..ec227fdddc5bd 100644 --- a/x-pack/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/plugins/graph/public/components/source_picker.tsx @@ -9,22 +9,28 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { CoreStart } from '@kbn/core/public'; -import { SavedObjectFinderUi } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { IndexPatternSavedObject } from '../types'; export interface SourcePickerProps { onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; http: CoreStart['http']; uiSettings: CoreStart['uiSettings']; + savedObjectsManagement: SavedObjectsManagementPluginStart; } const fixedPageSize = 8; -export function SourcePicker({ http, uiSettings, onIndexPatternSelected }: SourcePickerProps) { +export function SourcePicker({ + http, + uiSettings, + savedObjectsManagement, + onIndexPatternSelected, +}: SourcePickerProps) { return ( - { onIndexPatternSelected(indexPattern as IndexPatternSavedObject); }} diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 96dac017eeabb..feca5656f73d6 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -28,6 +28,7 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { checkLicense } from '../common/check_license'; import { ConfigSchema } from '../config'; @@ -44,6 +45,7 @@ export interface GraphPluginStartDependencies { inspector: InspectorPublicPluginStart; home?: HomePublicPluginStart; spaces?: SpacesApi; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export class GraphPlugin @@ -113,6 +115,7 @@ export class GraphPlugin uiSettings: core.uiSettings, spaces: pluginsStart.spaces, inspect: pluginsStart.inspector, + savedObjectsManagement: pluginsStart.savedObjectsManagement, }); }, }); diff --git a/x-pack/plugins/graph/public/services/source_modal.tsx b/x-pack/plugins/graph/public/services/source_modal.tsx index 258bb58d1e077..6da003833d1a9 100644 --- a/x-pack/plugins/graph/public/services/source_modal.tsx +++ b/x-pack/plugins/graph/public/services/source_modal.tsx @@ -8,6 +8,7 @@ import { CoreStart } from '@kbn/core/public'; import React from 'react'; import { KibanaReactOverlays } from '@kbn/kibana-react-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { SourceModal } from '../components/source_modal'; import { IndexPatternSavedObject } from '../types'; @@ -16,17 +17,20 @@ export function openSourceModal( overlays, http, uiSettings, + savedObjectsManagement, }: { overlays: KibanaReactOverlays; http: CoreStart['http']; uiSettings: CoreStart['uiSettings']; + savedObjectsManagement: SavedObjectsManagementPluginStart; }, onSelected: (indexPattern: IndexPatternSavedObject) => void ) { const modalRef = overlays.openModal( { onSelected(indexPattern); modalRef.close(); diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json index 0f9bc04b6975a..3979bb5b4d9a0 100644 --- a/x-pack/plugins/graph/tsconfig.json +++ b/x-pack/plugins/graph/tsconfig.json @@ -38,6 +38,8 @@ "@kbn/utility-types", "@kbn/react-field", "@kbn/shared-ux-router", + "@kbn/saved-objects-management-plugin", + "@kbn/saved-objects-finder-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx index f10718788eeab..bdf3958c79ca7 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useState } from 'react'; import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,60 +25,65 @@ export const DeleteDataStreamConfirmationModal: React.FunctionComponent = dataStreams: string[]; onClose: (data?: { hasDeletedDataStreams: boolean }) => void; }) => { + const [isLoading, setLoading] = useState(false); + const dataStreamsCount = dataStreams.length; const handleDeleteDataStreams = () => { - deleteDataStreams(dataStreams).then(({ data: { dataStreamsDeleted, errors }, error }) => { - const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length; + setLoading(true); + + deleteDataStreams(dataStreams) + .then(({ data: { dataStreamsDeleted, errors }, error }) => { + const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length; + + if (hasDeletedDataStreams) { + const successMessage = + dataStreamsDeleted.length === 1 + ? i18n.translate( + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText', + { + defaultMessage: "Deleted data stream '{dataStreamName}'", + values: { dataStreamName: dataStreams[0] }, + } + ) + : i18n.translate( + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText', + { + defaultMessage: + 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}', + values: { numSuccesses: dataStreamsDeleted.length }, + } + ); - if (hasDeletedDataStreams) { - const successMessage = - dataStreamsDeleted.length === 1 + onClose({ hasDeletedDataStreams }); + notificationService.showSuccessToast(successMessage); + } + + if (error || (errors && errors.length)) { + const hasMultipleErrors = + (errors && errors.length > 1) || (error && dataStreams.length > 1); + + const errorMessage = hasMultipleErrors ? i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText', + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText', { - defaultMessage: "Deleted data stream '{dataStreamName}'", - values: { dataStreamName: dataStreams[0] }, + defaultMessage: 'Error deleting {count} data streams', + values: { + count: (errors && errors.length) || dataStreams.length, + }, } ) : i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText', + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText', { - defaultMessage: - 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}', - values: { numSuccesses: dataStreamsDeleted.length }, + defaultMessage: "Error deleting data stream '{name}'", + values: { name: (errors && errors[0].name) || dataStreams[0] }, } ); - - onClose({ hasDeletedDataStreams }); - notificationService.showSuccessToast(successMessage); - } - - if (error || (errors && errors.length)) { - const hasMultipleErrors = - (errors && errors.length > 1) || (error && dataStreams.length > 1); - - const errorMessage = hasMultipleErrors - ? i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText', - { - defaultMessage: 'Error deleting {count} data streams', - values: { - count: (errors && errors.length) || dataStreams.length, - }, - } - ) - : i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText', - { - defaultMessage: "Error deleting data stream '{name}'", - values: { name: (errors && errors[0].name) || dataStreams[0] }, - } - ); - - notificationService.showDangerToast(errorMessage); - } - }); + notificationService.showDangerToast(errorMessage); + } + }) + .finally(() => setLoading(false)); }; return ( @@ -107,6 +112,7 @@ export const DeleteDataStreamConfirmationModal: React.FunctionComponent = values={{ dataStreamsCount }} /> } + isLoading={isLoading} > ; -export const logViewReferenceRT = rt.type({ +export const persistedLogViewReferenceRT = rt.type({ logViewId: rt.string, type: rt.literal('log-view-reference'), }); +export type PersistedLogViewReference = rt.TypeOf; + +export const inlineLogViewReferenceRT = rt.type({ + type: rt.literal('log-view-inline'), + id: rt.string, + attributes: logViewAttributesRT, +}); + +export const logViewReferenceRT = rt.union([persistedLogViewReferenceRT, inlineLogViewReferenceRT]); + export type LogViewReference = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts index 65bcec8c98e6a..f8daaa1b9227b 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts @@ -13,7 +13,7 @@ import { logEntryCursorRT, logEntryRT, } from '../../log_entry'; -import { logViewColumnConfigurationRT } from '../../log_views'; +import { logViewColumnConfigurationRT, logViewReferenceRT } from '../../log_views'; import { jsonObjectRT } from '../../typed_json'; import { searchStrategyErrorRT } from '../common/errors'; @@ -21,7 +21,7 @@ export const LOG_ENTRIES_SEARCH_STRATEGY = 'infra-log-entries'; const logEntriesBaseSearchRequestParamsRT = rt.intersection([ rt.type({ - sourceId: rt.string, + logView: logViewReferenceRT, startTimestamp: rt.number, endTimestamp: rt.number, size: rt.number, diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts index 2dc182c4ba0e2..6d2a7891264d1 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts @@ -7,12 +7,13 @@ import * as rt from 'io-ts'; import { logEntryCursorRT, logEntryFieldRT } from '../../log_entry'; +import { logViewReferenceRT } from '../../log_views'; import { searchStrategyErrorRT } from '../common/errors'; export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; export const logEntrySearchRequestParamsRT = rt.type({ - sourceId: rt.string, + logView: logViewReferenceRT, logEntryId: rt.string, }); diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index c6b18bebc98b3..1c38dca829aef 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -13,7 +13,7 @@ import { ForLastExpression, RuleTypeParamsExpressionProps, } from '@kbn/triggers-actions-ui-plugin/public'; -import { LogViewReference, ResolvedLogViewField } from '../../../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogViewField } from '../../../../../common/log_views'; import { Comparator, isOptimizableGroupedThreshold, @@ -54,7 +54,7 @@ const DEFAULT_BASE_EXPRESSION = { const DEFAULT_FIELD = 'log.level'; -const createLogViewReference = (logViewId: string): LogViewReference => ({ +const createLogViewReference = (logViewId: string): PersistedLogViewReference => ({ logViewId, type: 'log-view-reference', }); @@ -69,7 +69,7 @@ const createDefaultCriterion = ( const createDefaultCountRuleParams = ( availableFields: ResolvedLogViewField[], - logView: LogViewReference + logView: PersistedLogViewReference ): PartialCountRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, logView, @@ -82,7 +82,7 @@ const createDefaultCountRuleParams = ( const createDefaultRatioRuleParams = ( availableFields: ResolvedLogViewField[], - logView: LogViewReference + logView: PersistedLogViewReference ): PartialRatioRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, logView, @@ -226,11 +226,11 @@ export const Editor: React.FC createLogViewReference(logViewId), [logViewId]); + const logViewReference = useMemo(() => createLogViewReference(logViewId), [logViewId]); const defaultCountAlertParams = useMemo( - () => createDefaultCountRuleParams(supportedFields, logViewReferemnce), - [supportedFields, logViewReferemnce] + () => createDefaultCountRuleParams(supportedFields, logViewReference), + [supportedFields, logViewReference] ); const updateType = useCallback( @@ -238,12 +238,12 @@ export const Editor: React.FC { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx index 9b9dc501e1591..0b99cea2fd7c9 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx @@ -73,7 +73,7 @@ export const callGetChartPreviewDataAPI = async ( body: JSON.stringify( getLogAlertsChartPreviewDataRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, alertParams, buckets, }, diff --git a/x-pack/plugins/infra/public/containers/logs/log_entry.ts b/x-pack/plugins/infra/public/containers/logs/log_entry.ts index b9f95f3db6ab0..958097fc3baa5 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entry.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entry.ts @@ -30,7 +30,10 @@ export const useLogEntry = ({ return !!logEntryId && !!sourceId ? { request: { - params: logEntrySearchRequestParamsRT.encode({ sourceId, logEntryId }), + params: logEntrySearchRequestParamsRT.encode({ + logView: { type: 'log-view-reference', logViewId: sourceId }, + logEntryId, + }), }, options: { strategy: LOG_ENTRY_SEARCH_STRATEGY }, } diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx index fc1026243a7e0..d9f7ab078ae0b 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -37,7 +37,7 @@ export const useLogEntryHighlights = ( return await fetchLogEntriesHighlights( { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp, endTimestamp, center: centerPoint, diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts index 59abb716f6cb3..e994e2a013bb7 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts @@ -39,7 +39,7 @@ export const useLogSummaryHighlights = ( return await fetchLogSummaryHighlights( { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts index 1e9e1dd48a47b..3ee39fbda3d49 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts @@ -58,7 +58,7 @@ export const useLogEntriesAfterRequest = ({ highlightPhrase, query: query as JsonObject, size: params.size, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp, }), }, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts index 6e72fce5a1d2b..581d31a28a8c5 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts @@ -58,7 +58,7 @@ export const useLogEntriesBeforeRequest = ({ highlightPhrase, query: query as JsonObject, size: params.size, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp: params.extendTo ?? startTimestamp, }), }, diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx index 25e02101984f4..fe02367e4b1ff 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx @@ -62,7 +62,7 @@ describe('useLogSummary hook', () => { expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( expect.objectContaining({ - sourceId: 'INITIAL_SOURCE_ID', + logView: { logViewId: 'INITIAL_SOURCE_ID', type: 'log-view-reference' }, }), expect.anything() ); @@ -74,7 +74,7 @@ describe('useLogSummary hook', () => { expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( expect.objectContaining({ - sourceId: 'CHANGED_SOURCE_ID', + logView: { logViewId: 'CHANGED_SOURCE_ID', type: 'log-view-reference' }, }), expect.anything() ); diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx index b3e7f8235ace7..c4b933ab04cd0 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx @@ -35,7 +35,7 @@ export const useLogSummary = ( pushLogSummaryBucketsArgs([ { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts index d4053d14ab304..96b7dbb687851 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts @@ -23,7 +23,7 @@ import type { import { initializeFromUrl, updateContextInUrl } from './url_state_storage_service'; export const createPureLogStreamPositionStateMachine = (initialContext: LogStreamPositionContext) => - /** @xstate-layout N4IgpgJg5mDOIC5QBsD2UDKAXATmAhgLYAK+M2+WYAdAK4B2Alk1o-sowF6QDEAMgHkAggBEAkgDkA4gH1BsgGpiAogHUZGACpCASpuUiA2gAYAuolAAHVLEatU9CyAAeiALQAmAJzUPHgGwArIEALAAcHmHhXsEhADQgAJ6IAQDM1IHGYQDs2ZnZxl5e-l6pAL5lCWiYuAQkZGAUVHRMLGwc3BD8wuLScgKKKuoAYkJifAYm5kgg1rb2jjOuCJ4hAIzUqZklHgX+xqlrkQnJCKlB1P4loYH+qV7hHoEVVejYeESk5FiUNAzMdnaXF4glEklk8hkSjUGgAqgBheHKAyTMxOOaAhxOZbZNbZajGXLBVIkrJrYInRBHYzUEIeVIhVJhNZZLws7IhF4garvOpfRo-Zr-NrsYFdUG9CEDKFDOGI5EiSZraZWGyYxagHEhEIEwkeI7Zc7GO6UhCZHzZML7MKBDwhQp0zmVblvWqfBpNGhofAQZhQPjoBSMMAAd26YL6kOhIzGEyMaJmGIW2JS4VpJTCWXp5K82Q8pvN1Et1tt9oedq5PLd9W+v2o3t99H9geDYYl4P6gxhGARSJR8ZVszVyaWiEZPnt-m8Ry8xjta38pqN1FK+uy+w8xhCgS8lddHxrArrDb9AagQdD4clnZl3d7CqVg6TjCxo4QISumy2MWNMWiAVNO58WMFkImMOc50CMI9xqA9+U9etUB9U8W1DYZ8EYZAQR6Dso1lLRdH0Ad0WHF8NRcdxvF8AJYgiKIwhiUIC1SDwMgNcC8g5O1nmdKs4I9QUaAAC3wWAzwvEMxHoX0AGM4BaAFWFFToeB0ZQkTEBQDBkSQxE0MQhD4GRiF0IQAFllH0HQMCmEj5jIlMEBnfxqAiYojjWVJCjnJcwnSIIrSuNZZ2CGJyl4-c+QEusRLE1DJOkxg5NgBSRQ6XgFEMsQRBkABFWFlB0ABNGR4QACSEaRUSfUjX01Kl-DyWk1giW0p3tElTTxfFwhCPYQIXXE1idV5YKi2tmli8TWyk2T5OFQFlN4SRMr4bK8oK4rSoqqriMTWryOWBdGtckCQMCEknn8MIl21FcWLxVJDUzfwWv8GDeXdCbhNE6bQ1mpL5MUoEVNW9b8sKkrysqqRqrs9VHMGwJmtagI7QOVICznFd1yebdMgutY1gqZ16FQCA4CcPjxqPKh4ZHeqVkiHwtl-XZjQOTzTTcNk2NtQ4-A5q0PureDBNSxb0ogemHLfOkurpahyXArIbTZIItxF-jvsQ5Cmz+kMZbqiiEF2DJQiuK0bVyH94iSRAmTCTZ3ICPMtj8HjRs+w8EJPfX4vQzDICNw7EGR5k7S8NJGrZNXAJ3IsnvtImt28aCIrGr7aZ+uLzxmxLkpDxHNxpC7dn2K404pe331YrwokNQ1GIF7ItZphCpvigHkolpSpaLt8jjTMv12NKd6+r04nlYgbDjxO0-KnNus4736u4LoG0rFAfGc8qIi0Cck1cCQ1K4LJ4iznS1zjzG0WOXn3xcIRhYFsf28-+jf4H2+zjaOw4XKbijt4YIWRbjZHjhaJ6T1cSwIxiTMoQA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBsD2UDKAXATmAhgLYAKqsAlluagHbb5ZgB0Arjee1fsuQF6QBiAEoBRAMIiAkgDURAEQD6kgHKSAKpICCAGQUBFAKoihATQXFNQzQFkRa4xgDaABgC6iUAAcylajQ8gAB6IALQAbACsTAAcAEzRzgCcACzJccmxAIzR0QA0IACeiNkAzExhJSUA7FURsSXRYYklEVUAvm35aJi4BCQ+VLT0jEwcvtx8HFAAYjiohAY4yAIq6lrakgBa8grTQgDy1goGQtou7kgg3hSD-pfBCCGxTBHREYnOTelZ0bX5RQg0okmD8miVMhEwmEqmFGh0uuhsHgiKQbn5hswxlwePwIExrr5aLBRpxyBNcQIAFIGazEBRqfb0ywAcTs5n2GDW+2U5wCBNuAQeVUSz2SULCmQ+zlScQi-0QyWcVSYVU+QIhzlejQi8JA3SRfVRhLoWAYmNJ5Mg+IGfmJWLJOMEomI+yEagU0kknIAQtoROzORpuU43HybbRBYg6mEmGloskqrEqjkwrFYvLAdEyhkItLMlVqtVc+1OnrEb0UeGTWaSeNHXj+bba9i+IINLYFGIABKaZSsuS8y6NiP3RDC57ChOpZzStKxOWFRBZMqpqr5xIpT6xEXRXX6iv9NFDU0je2WvFYAAWcywWB4NCgxHwMBENAgylQVAAZuQAMYMJtyAgZAwGEEQXTdD0vUkX1-RdQNJGDQcvCrSMEHeTJygSWJkg+X5FQhDN4xjNNIk1V4xRKWIwj3ctkUPY0MWbB1Wwva9PzvKYnxfN8P2-P8AKJJgrxvTiHzAiD3U9H0-QDLllBDC4UKPO5QAeTJJTKVVITTRpt3iDMaiiCINUSDTMlw-Nklonp6KNW4mLPethPY2970fZ8wFfd9P3IH9-1uYkRI49yBECWAT2YfAv0YHAAApRG0TQNFkBQRGURQDGIORkv9OQRCSkwAEoBH3Oyq0ci1nOCtyuM87y+L8gTApc0T3OQq5UNHBAzOiFVohFEo5xKZx6mSDNeqYDTakyVMlWiTIhpsg1KxUyq61Y1qQrqnifP4gKmxqsSoDCiKa2i2KEoK5KZH9dLMuy3KFHywqSrKw0Ksi5jzy22qH24rzeN8-zBJoILXOOxxMiUzqVLQ2plTqEoRWnGcUgmpopvjRUYWSbJWhKZaD3s9EvqczajvcgGGuB5qmxoYGCimAQOuHVSggVGpymhMJnAhVUEjqDNMiVKJaiG+dfgG6prN1BmIDgAJ3tWxjIrDOHupCDSyjiBIUjnDJsjyRdHjSGJEwWjd8zeNGifKtavrYcncXV400IyIjMI3b36lVXmIgTO2PodmtnamWZ5kWZBXYFTXnmlWE-hNka+uoxIA6zRJZUJ0tlYYhyyaq1iY78NCSkiFULLnSFkYlaEMwacok2yGcRZF1pMiDlWC9DovcWtFT4CHLq1IVRaYnjapEmTRNnHBYXqKm5vhWaBIJTqLv89J3uNv7tm7T7yAS5HUfAQlCfkinmfYjnzIM23KI04z5Hs83knjx3lt+8pnbAb2pqDpEmPuzB4eMtJV1lBURIdcqgZlhH1PmJkcJDTqItEsCJbLB1Vp-Fi38IZU3qkDfaoM7TATAMA92581wynnFAmBRExQgmormCIFEKjUTfp9HBP0f7-UIf-EGLVeFQAod1cyzx4isKqHOfMkIGEkWYeRYiVEaK5zolgnup5D5sTar-GmxCWoM2-EzB8ojT5t2BK8HClFFqREIibduzgVQRCGiNJUyRWEig6B0IAA */ createMachine( { context: initialContext, @@ -92,7 +92,7 @@ export const createPureLogStreamPositionStateMachine = (initialContext: LogStrea }, throttling: { after: { - [RELATIVE_END_UPDATE_DELAY]: [ + RELATIVE_END_UPDATE_DELAY: [ { target: 'notifying', cond: 'hasReachedPageEndBuffer', @@ -185,6 +185,9 @@ export const createPureLogStreamPositionStateMachine = (initialContext: LogStrea : {} ), }, + delays: { + RELATIVE_END_UPDATE_DELAY, + }, guards: { // User is close to the bottom of the page. hasReachedPageEndBuffer: (context, event) => diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts index bbae54bf8803e..16ddeb9352a31 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts @@ -55,7 +55,7 @@ import { DEFAULT_REFRESH_INTERVAL, DEFAULT_REFRESH_TIME_RANGE } from './defaults export const createPureLogStreamQueryStateMachine = ( initialContext: LogStreamQueryContextWithDataViews & LogStreamQueryContextWithTime ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEUCuYBOBPAdKgdgJZEAuhAhgDaEBekAxANoAMAuoqAA4D2shZ3fBxAAPRAEYAHAGYck8QCYArAoAsC8QHZNygJySANCCwTpszc2njpANnH2FN1UtUBfV0bSZcxfhWo0xFAAYhjcALYAqhiU9ACSAHJxACpxAIIAMnEAWgCiACIA+sEASgDyALKFkSUZLOxIIDx8AkKNYghKzJJyCvJOurqqA4bGiPI4XczTdjbM2kq64u6e6Ng4vmRUtEGhEdGxiSnpWXlFpZXVtYziDVy8foLCHSpGJgjizj02SjYa3X0dH8ViAvOtNv4dvgQmFwslCOEwMFCJQSJgAMqYABuhAAxmB4klUpkcgViuUqqkKrlinEMslciVCujGQA1OIAYVy9WEzUebVAHWkkhsOGmmnkqnUumkzBsmmkb0Quk0ot0coUZnscukSkkILBPlIkLoEBwAEc1lh6MhIoyAJrky4stIlDkACUKACFXYUPWkEgBxAo8xp81rPcbiJQ4GzSPoKOXMX7RxVjD7MVTiOTyxOSVT5zRKCUGq0bY3bU0Wq30YJ0hkldFOqout2en1M-1BkNsXkPCPtKMxuMJpMppRp95LHrDZi6CfSbQ2STF0vect+SuQaveej5NLJNKFdm5ADqTa7wfyofuLUIT0HCHkw-jkkTc3Hk-GCgUYvm8kGAtCzXcEKwCbdLXXLFtggcgyGhehWRJfdUjKBJmUiDkuQKHs7iaft7wFUQJCzVQcGkdU7GjOdxF0JUECGZhyMUJRFGYBMVX1DxQTLCEtzNSD1mg6hYPgqBEOQg84jQ4o0jpXC+zvB9BRIz5yMo+wuiWOj03sOYcC0Wi+gA7RkxAo1N3AgSywwMBhMIUSggkrIUOk9DgjkjIFLDAjlOIj5SPUuVNJonT3nESxRVVX4FU0RQbH0aRzI3LYrJ3dZbPsxyEKQlypJk9FMOw-JvNvflIwCtSKOC6jtPo-ocEo5QEo0H9fmSvi0rIRF6CpGkLkpOJqVpelGWZNlORpS9SvwpSiI6Z9Y1fd9kzsCd6KkVRNFjX5-l0BQLCLLjVnXTraG3bqCUiAAFFCaT6woSgDYMb1m8rH0Wkc3zHNavw+eUmOkVRF2XFVhjmGwOrA86zUu+gbrux7clKXJ0U9RIG1y17w0IirPuWn7Uw21QZTFKx4wsH81CUJQocsmGcEulKTQYbHfPm0ws0mTVweBiV51UDadF0SYZDMYHJE0KUtrp1KGaZs7TSYW5FPelSPiB7MVCBmwnEXSQBaFyQem6LUpYogsdHcbj8G4CA4GEQ1VYHdWAFobHo12Y0GH3fb9pLuMNPAiGh01ndxx91A2+McAsKw7F1I7AVlk1dlhA5w78joDp6A383lRctC0wXdLnHBEysTU40UA2ZcD3jQ7TiJ4URZFUQxbE8TATOOYQeV6MGQHFkXRd51owYU-4nuKrikWvpWz96JJpimrsRwJQLaNJ7SwT3jKl3-NVbb58J9b02LGcNXJn8Cx+beGd3nAsrgoJp8fexJfL+Q2q0AtbBLqcLhJgUQiuIP4zgwHHR4qdUOEEyxZTfurMButYzzllBROYUo-j1WcDgKUjhjaEMsNYe+VZH7EAQT5OaFVkGigShOSwuhMHDAUBtOUMZZQaGBrqXUIo3D1xgfTMhNk7IwRftCRB-kP7bT6IoZQv8ZBOBwWROYMoZQ-klpITMpCLoIm7lQtWh95RLVHB+X60cY4TmsOqQuqot4CNAkI3RiJmZTwMQfDoigtCNWilote0wlBCyBuXEm+hpQWF1jo2GeicCwBIC-XEkjPGsTIrRCcE5dYS1okbUUZgzAHWTEDSwUTGYxLibZcg4RX7uIjkglJBk0EZL1gBIWB05B5OjFLYs+hNDW1cEAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEUCuYBOBPAdKgdgJZEAuhAhgDaEBekAxANoAMAuoqAA4D2shZ3fBxAAPRAEYAHAGYck8QCYArAoAsC8QHZNygJySANCCwTpszc2njpANnH2FN1UtUBfV0bSZcxfhWo0xFAAYhjcALYAqhiU9ACSAHJxACpxAIIAMnEAWgCiACIA+sEASgDyALKFkSUZLOxIIDx8AkKNYghKzJJyCvJOurqqA4bGiPI4XczTdjbM2kq64u6e6Ng4vmRUtEGhEcmE4WDBhJQkmADKmABuhADGYPFJqZk5BcXlVakVucVxGclciVChcgQA1OIAYVy9WEzT8gmEHWkkhsOGmmnkqnUumkzBsmmkRhMCF0mjRunxCjM9nx0iUkhWIC8602-lokBwAEc1lh6MhIkCAJofSog3JpEqQgAShQAQpLCjK0gkAOIFWGNeGtJHjcRKHA2aR9BT45hKOxKIljBDiZiqcRyAmmySqV2aJSYpksnykdl0CDc3n0YL-QElC6iqqgyUy+WK5VqjVsOG8BFtUAdeQGo0ms0W-XWklLHrDZi6K3SbQ2SSe728jZ+7YBoPeej5NLJNKFCG5ADqkcT6vymq4aZ17T1OeNklNcwLVuJ4wUCnR83kgzd7vr3kbfmbnJ5u+u2wg5DI+Cg9DBrw7qTKCRBkUh0IKyYaY5ahERk9tDtUODSJSdj6uW4i6EupKqMwgGKEoijMCaZKMh4zINmyB6Bke6wntQZ4XleN5ZHecQPsUaT-O+qZfj+mYSP+gHAfYXRLBBNr2HMOBaOBfQbto5o7qyTYBIeDYYGAuGEPhQTXrenakY+wQURkVFauO34ZqI9HiABQH4sxYFsSSdq2Dg5IWoSmiKDY+jSIJvr7iJWFiRJp7njJRFxCRZEXM+r75Kpn7prqf46Yx+mgaxkH9DgwHKDZGgrha9l7lsTk4GQRz0N8vylGKOV-ACQLiiUELQkq0oqsOo5NOptFaQg2aGjOc7mpaRYSO6hoWho5YKBYHooasu4YelmWPJEAAKd6-AVJRVTCKZqTRmlZvqzV5vO7WQeIBIwdIqhVjWZLDHMNgpaNHKBuN9BTTNhQlLkpS5BcsqJOGRE1dqGkhU1uazvm23saouLolYxoWCuahKEoF3CVdGWHGAqX+gwX11atpgOpM1KnYdmIVqoO06LokwyGYh2SJo2KqJocOOQj40o5hTDiB+tUrSF1jYyoB02E4VaSITxOSD03Q0tTQFujo9NpYzSM4LAJDuXc9CTWk6qFLkCRFHKkTBMExWPWkMqBRzwW-rtFjosMQv0rSpqaDtVMAXauKYrO8HMDpsuo9dCtK+J5DhDJIhK+eyPkAAZucGAABTiVH4mwAAFgAlPy6Hwy2TOB2AwdBOjnOW4SpNC+6qICxuxMooBZgQ+aB2WO4qH4NwEBwMIPrURbdEIAAtDYkH9wagxj97tjDCuVZuKhPp4EQ2eQD3E59+oO3GmZli7dYnoMn0dNz1nDOBJeexRDEK8-b+-U9OXwyWVoLFE+x5Y4KaVjUkaihC7TvvNrsMI4QDhHBOGcS4Nx7hgCvvVDoBJIKDH2osKsVYKzgUGP-JyMDMYICsqTf6rUFwdQQCDGCcUpC6BNPIOyR8RpL2ct4bBIVySaA2gDLahZIKelLFSU0iE5hkkwQjbCuBJLSUvEwy2UhWF9EUMoLQbpJ4IJcJMICdpdrQ12kNNCdCT6iWPKeSRfddr80NBWPEQE5jYhsAoaKzgcDYkcKLZx28aHDSEnohhQkxFGIaiYtENkrSWF0FYqeO18QGjxBoQ69J6Solnu4hycsWwiJwOJMR7kJHLV7n46R795BJQUTIJwdiAICKAsaA+kh7RCJzkjXxHQWFsMIUDYyeI0RWmsALBCNZYa0I8ckzkTNLoBgaRIDQrCyQ2AZPpPhSga4ATUIMV0CgyT4nOv0pJftEZHEVsrMgdwxm2ngq7cxVp+aU3AiLNE9cG5Wmgm4nRAztm5xIEHEOWSgqrz8ScriZzbBVyuexHQrDyZWE9M4TQ+hD7uCAA */ createMachine( { context: initialContext, @@ -281,7 +281,10 @@ export const createPureLogStreamQueryStateMachine = ( updateTimeContextFromTimeRangeUpdate, updateTimeContextFromRefreshIntervalUpdate, refreshTime: send({ type: 'UPDATE_TIME_RANGE', timeRange: DEFAULT_REFRESH_TIME_RANGE }), - expandPageEnd: send({ type: 'UPDATE_TIME_RANGE', timeRange: { to: 'now' } }), + expandPageEnd: send((context) => ({ + type: 'UPDATE_TIME_RANGE', + timeRange: { to: context.timeRange.to }, + })), updateTimeContextFromUrl, }, guards: { diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts index 3bc2adcb79315..c7ce0ac8b9c18 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts @@ -143,7 +143,7 @@ export const updateTimeContextFromRefreshIntervalUpdate = actions.assign( ? { timestamps: { startTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.from, 'down') ?? 0, - endTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.to, 'down') ?? 0, + endTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.to, 'up') ?? 0, lastChangedTimestamp: nowTimestamp, }, } @@ -170,7 +170,7 @@ const getTimeFromEvent = (context: LogStreamQueryContext, event: LogStreamQueryE ? datemathToEpochMillis(from, 'down') : context.timestamps.startTimestamp; const toTimestamp = event.timeRange?.to - ? datemathToEpochMillis(to, 'down') + ? datemathToEpochMillis(to, 'up') : context.timestamps.endTimestamp; return { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts index 51d4a69ada295..34358b983e12b 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts @@ -31,7 +31,7 @@ export const callGetLogEntryCategoryDatasetsAPI = async ( body: JSON.stringify( getLogEntryCategoryDatasetsRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts index 90727fd6f0853..e3b99750af715 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts @@ -35,7 +35,7 @@ export const callGetLogEntryCategoryExamplesAPI = async ( data: { categoryId, exampleCount, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts index 9472991e15c66..93e9daf0b9cb6 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -36,7 +36,7 @@ export const callGetTopLogEntryCategoriesAPI = async ( body: JSON.stringify( getLogEntryCategoriesRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts index 5f0f95a3e7976..7916cad0f1e07 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts @@ -30,7 +30,7 @@ export const callGetLogEntryAnomaliesAPI = async (requestArgs: RequestArgs, fetc body: JSON.stringify( getLogEntryAnomaliesRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts index 9b560845186f7..16a8092f290f8 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts @@ -29,7 +29,7 @@ export const callGetLogEntryAnomaliesDatasetsAPI = async ( body: JSON.stringify( getLogEntryAnomaliesDatasetsRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts index 5844e00ebfe4e..0e44e5b02feb7 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts @@ -32,7 +32,7 @@ export const callGetLogEntryExamplesAPI = async (requestArgs: RequestArgs, fetch data: { dataset, exampleCount, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx index ed5d0f5831ca6..1b86c5a543d06 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx @@ -22,6 +22,7 @@ export const Tile = ({ type, ...props }: Props) => { metrics: [{ type }], groupBy: null, includeTimeseries: true, + dropPartialBuckets: false, }); return ; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx index cb31f2c5e9102..7933d3bdb9f3c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx @@ -49,7 +49,7 @@ export const AlertsTabContent = () => { return ( - + diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx index 534d28e671fe3..1333c5b5c3439 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx @@ -40,7 +40,11 @@ export const AlertsTabBadge = () => { typeof alertsCount?.activeAlertCount === 'number' && alertsCount.activeAlertCount > 0; return shouldRenderBadge ? ( - + {alertsCount?.activeAlertCount} ) : null; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts index 3363a307061f2..f0506433f514c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts @@ -13,7 +13,6 @@ export const ALERT_STATUS_ALL = 'all'; export const ALL_ALERTS: AlertStatusFilter = { status: ALERT_STATUS_ALL, - query: '', label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.showAll', { defaultMessage: 'Show all', }), @@ -21,7 +20,13 @@ export const ALL_ALERTS: AlertStatusFilter = { export const ACTIVE_ALERTS: AlertStatusFilter = { status: ALERT_STATUS_ACTIVE, - query: `${ALERT_STATUS}: "${ALERT_STATUS_ACTIVE}"`, + query: { + term: { + [ALERT_STATUS]: { + value: ALERT_STATUS_ACTIVE, + }, + }, + }, label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.active', { defaultMessage: 'Active', }), @@ -29,7 +34,13 @@ export const ACTIVE_ALERTS: AlertStatusFilter = { export const RECOVERED_ALERTS: AlertStatusFilter = { status: ALERT_STATUS_RECOVERED, - query: `${ALERT_STATUS}: "${ALERT_STATUS_RECOVERED}"`, + query: { + term: { + [ALERT_STATUS]: { + value: ALERT_STATUS_RECOVERED, + }, + }, + }, label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.recovered', { defaultMessage: 'Recovered', }), diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts index 11dde6d4b666b..134991122f1c2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts @@ -8,11 +8,11 @@ import { useCallback, useMemo, useState } from 'react'; import createContainer from 'constate'; import { getTime } from '@kbn/data-plugin/common'; import { TIMESTAMP } from '@kbn/rule-data-utils'; -import { BoolQuery, buildEsQuery, Filter, Query } from '@kbn/es-query'; +import { BoolQuery, buildEsQuery, Filter } from '@kbn/es-query'; import { SnapshotNode } from '../../../../../common/http_api'; import { useUnifiedSearchContext } from './use_unified_search'; import { HostsState } from './use_unified_search_url_state'; -import { useHostsView } from './use_hosts_view'; +import { useHostsViewContext } from './use_hosts_view'; import { AlertStatus } from '../types'; import { ALERT_STATUS_QUERY } from '../constants'; @@ -21,7 +21,7 @@ export interface AlertsEsQuery { } export const useAlertsQueryImpl = () => { - const { hostNodes } = useHostsView(); + const { hostNodes } = useHostsViewContext(); const { unifiedSearchDateRange } = useUnifiedSearchContext(); @@ -65,22 +65,21 @@ const createAlertsEsQuery = ({ hostNodes: SnapshotNode[]; status?: AlertStatus; }): AlertsEsQuery => { - const alertStatusQuery = createAlertStatusQuery(status); + const alertStatusFilter = createAlertStatusFilter(status); const dateFilter = createDateFilter(dateRange); const hostsFilter = createHostsFilter(hostNodes); - const queries = alertStatusQuery ? [alertStatusQuery] : []; - const filters = [hostsFilter, dateFilter].filter(Boolean) as Filter[]; + const filters = [alertStatusFilter, dateFilter, hostsFilter].filter(Boolean) as Filter[]; - return buildEsQuery(undefined, queries, filters); + return buildEsQuery(undefined, [], filters); }; const createDateFilter = (date: HostsState['dateRange']) => getTime(undefined, date, { fieldName: TIMESTAMP }); -const createAlertStatusQuery = (status: AlertStatus = 'all'): Query | null => - ALERT_STATUS_QUERY[status] ? { query: ALERT_STATUS_QUERY[status], language: 'kuery' } : null; +const createAlertStatusFilter = (status: AlertStatus = 'all'): Filter | null => + ALERT_STATUS_QUERY[status] ? { query: ALERT_STATUS_QUERY[status], meta: {} } : null; const createHostsFilter = (hosts: SnapshotNode[]): Filter => ({ query: { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index 41e476dbf12c5..2c7eb84dd43bf 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -79,7 +79,8 @@ export const useHostsUrlState = () => { const getDateRangeAsTimestamp = useCallback(() => { const from = DateMath.parse(state.dateRange.from)?.valueOf() ?? getDefaultFromTimestamp(); - const to = DateMath.parse(state.dateRange.to)?.valueOf() ?? getDefaultToTimestamp(); + const to = + DateMath.parse(state.dateRange.to, { roundUp: true })?.valueOf() ?? getDefaultToTimestamp(); return { from, to }; }, [state.dateRange]); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts index b6b1407a0f5b6..6b948fb0da6c9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { Filter } from '@kbn/es-query'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import { ALERT_STATUS_ALL } from './constants'; @@ -15,6 +16,6 @@ export type AlertStatus = export interface AlertStatusFilter { status: AlertStatus; - query: string; + query?: Filter['query']; label: string; } diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 8818fac0e39ee..ae39a5f03ea7e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -34,6 +34,7 @@ export function useSnapshot({ groupBy = null, sendRequestImmediately = true, includeTimeseries = true, + dropPartialBuckets = true, requestTs, ...args }: UseSnapshotRequest) { @@ -56,6 +57,7 @@ export function useSnapshot({ lookbackSize: 5, }, includeTimeseries, + dropPartialBuckets, }; const { error, loading, response, makeRequest } = useHTTPRequest( diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index e62fd9291fc33..bd033b5285b3e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -158,7 +158,10 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const [, , { logViews }] = await libs.getStartServices(); const logQueryFields: LogQueryFields | undefined = await logViews .getClient(savedObjectsClient, esClient) - .getResolvedLogView(sourceId) + .getResolvedLogView({ + type: 'log-view-reference', + logViewId: sourceId, + }) .then( ({ indices }) => ({ indexPattern: indices }), () => undefined diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index 37eb4698ff089..1d0470c244fd1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -188,7 +188,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => const { indices, timestampField, runtimeMappings } = await logViews .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser) - .getResolvedLogView(validatedParams.logView.logViewId); + .getResolvedLogView(validatedParams.logView); if (!isRatioRuleParams(validatedParams)) { await executeAlert( diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 1e0daad376a1a..fcda9b30b0dac 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -15,6 +15,7 @@ import { LogColumn, LogEntry, LogEntryCursor } from '../../../../common/log_entr import { LogViewColumnConfiguration, logViewFieldColumnConfigurationRT, + LogViewReference, ResolvedLogView, } from '../../../../common/log_views'; import { decodeOrThrow } from '../../../../common/runtime_types'; @@ -66,7 +67,7 @@ export class InfraLogEntriesDomain { public async getLogEntriesAround( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, params: LogEntriesAroundParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { @@ -84,7 +85,7 @@ export class InfraLogEntriesDomain { const { entries: entriesBefore, hasMoreBefore } = await this.getLogEntries( requestContext, - sourceId, + logView, { startTimestamp, endTimestamp, @@ -110,7 +111,7 @@ export class InfraLogEntriesDomain { const { entries: entriesAfter, hasMoreAfter } = await this.getLogEntries( requestContext, - sourceId, + logView, { startTimestamp, endTimestamp, @@ -126,7 +127,7 @@ export class InfraLogEntriesDomain { public async getLogEntries( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, params: LogEntriesParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { @@ -134,7 +135,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const columnDefinitions = columnOverrides ?? resolvedLogView.columns; const messageFormattingRules = compileFormattingRules( @@ -184,7 +185,7 @@ export class InfraLogEntriesDomain { public async getLogSummaryBucketsBetween( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, start: number, end: number, bucketSize: number, @@ -194,7 +195,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( requestContext, resolvedLogView, @@ -208,7 +209,7 @@ export class InfraLogEntriesDomain { public async getLogSummaryHighlightBucketsBetween( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, startTimestamp: number, endTimestamp: number, bucketSize: number, @@ -219,7 +220,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const messageFormattingRules = compileFormattingRules( getBuiltinRules(resolvedLogView.messageField) ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index 22bf5466ee3b4..b17afa68d2d4d 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -16,7 +16,7 @@ import { logEntryRateJobTypes, Pagination, } from '../../../common/log_analysis'; -import { ResolvedLogView } from '../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { @@ -54,11 +54,11 @@ interface MappedAnomalyHit { async function getCompatibleAnomaliesJobIds( spaceId: string, - sourceId: string, + logViewId: string, mlAnomalyDetectors: MlAnomalyDetectors ) { - const logRateJobId = getJobId(spaceId, sourceId, logEntryRateJobTypes[0]); - const logCategoriesJobId = getJobId(spaceId, sourceId, logEntryCategoriesJobTypes[0]); + const logRateJobId = getJobId(spaceId, logViewId, logEntryRateJobTypes[0]); + const logCategoriesJobId = getJobId(spaceId, logViewId, logEntryCategoriesJobTypes[0]); const jobIds: string[] = []; let jobSpans: TracingSpan[] = []; @@ -99,7 +99,7 @@ export async function getLogEntryAnomalies( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, sort: AnomaliesSort, @@ -114,7 +114,7 @@ export async function getLogEntryAnomalies( timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( infraContext.spaceId, - sourceId, + logView.logViewId, infraContext.mlAnomalyDetectors ); @@ -155,7 +155,7 @@ export async function getLogEntryAnomalies( const logEntryCategoriesCountJobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -331,7 +331,7 @@ export async function getLogEntryExamples( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, dataset: string, @@ -345,7 +345,7 @@ export async function getLogEntryExamples( const jobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, categoryId != null ? logEntryCategoriesJobTypes[0] : logEntryRateJobTypes[0] ); @@ -370,7 +370,7 @@ export async function getLogEntryExamples( timing: { spans: fetchLogEntryExamplesSpans }, } = await fetchLogEntryExamples( context, - sourceId, + logView, indices, runtimeMappings, timestampField, @@ -397,7 +397,7 @@ export async function fetchLogEntryExamples( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, indices: string, runtimeMappings: estypes.MappingRuntimeFields, timestampField: string, @@ -420,7 +420,7 @@ export async function fetchLogEntryExamples( const logEntryCategoriesCountJobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -483,7 +483,7 @@ export async function getLogEntryAnomaliesDatasets( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number ) { @@ -492,7 +492,7 @@ export async function getLogEntryAnomaliesDatasets( timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( context.infra.spaceId, - sourceId, + logView.logViewId, context.infra.mlAnomalyDetectors ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index 9152945002e2a..d8eb18e4890b5 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -15,7 +15,7 @@ import { logEntryCategoriesJobTypes, } from '../../../common/log_analysis'; import { LogEntryContext } from '../../../common/log_entry'; -import { ResolvedLogView } from '../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; @@ -47,7 +47,7 @@ export async function getTopLogEntryCategories( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, categoryCount: number, @@ -59,7 +59,7 @@ export async function getTopLogEntryCategories( const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -119,13 +119,13 @@ export async function getLogEntryCategoryDatasets( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number ) { const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -143,7 +143,7 @@ export async function getLogEntryCategoryExamples( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, categoryId: number, @@ -154,7 +154,7 @@ export async function getLogEntryCategoryExamples( const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts index 7da6298fff76a..1e043fed0986a 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts @@ -24,13 +24,13 @@ export async function getLogEntryRateBuckets( spaceId: string; }; }, - sourceId: string, + logViewId: string, startTime: number, endTime: number, bucketDuration: number, datasets?: string[] ) { - const logRateJobId = getJobId(context.infra.spaceId, sourceId, 'log-entry-rate'); + const logRateJobId = getJobId(context.infra.spaceId, logViewId, 'log-entry-rate'); let mlModelPlotBuckets: LogRateModelPlotBucket[] = []; let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined; diff --git a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts index 95b0c8320559e..fbc530397f4e3 100644 --- a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts +++ b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts @@ -29,11 +29,11 @@ export const initGetLogAlertsChartPreviewDataRoute = ({ }, framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { - data: { sourceId, buckets, alertParams }, + data: { logView, buckets, alertParams }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const { series } = await getChartPreviewData( diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts index dd6254cf560e2..13df82f8fe343 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts @@ -31,7 +31,7 @@ export const initGetLogEntryAnomaliesRoute = ({ framework }: InfraBackendLibs) = framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, sort: sortParam, pagination: paginationParam, @@ -51,7 +51,7 @@ export const initGetLogEntryAnomaliesRoute = ({ framework }: InfraBackendLibs) = timing, } = await getLogEntryAnomalies( infraMlContext, - sourceId, + logView, startTime, endTime, sort, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts index 1d1f620063b2e..5f7aec90376af 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts @@ -29,7 +29,7 @@ export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBacken framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; @@ -39,7 +39,7 @@ export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBacken const { datasets, timing } = await getLogEntryAnomaliesDatasets( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime ); diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts index 6e2e8e8a6c2ad..1a484a0662e05 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts @@ -31,7 +31,7 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) data: { categoryCount, histograms, - sourceId, + logView, timeRange: { startTime, endTime }, datasets, sort, @@ -43,7 +43,7 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) const { data: topLogEntryCategories, timing } = await getTopLogEntryCategories( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime, categoryCount, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts index de5ac9dac4b07..92f0cd576a0f8 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts @@ -29,7 +29,7 @@ export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackend framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; @@ -39,7 +39,7 @@ export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackend const { data: logEntryCategoryDatasets, timing } = await getLogEntryCategoryDatasets( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime ); diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts index b51aed45b7e11..40de491c1673f 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts @@ -34,20 +34,20 @@ export const initGetLogEntryCategoryExamplesRoute = ({ data: { categoryId, exampleCount, - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); const { data: logEntryCategoryExamples, timing } = await getLogEntryCategoryExamples( { infra: await infraMlContext.infra, core: await infraMlContext.core }, - sourceId, + logView, startTime, endTime, categoryId, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts index fb82a2cd90df5..23ba1072a60fc 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts @@ -34,21 +34,21 @@ export const initGetLogEntryExamplesRoute = ({ data: { dataset, exampleCount, - sourceId, + logView, timeRange: { startTime, endTime }, categoryId, }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); const { data: logEntryExamples, timing } = await getLogEntryExamples( infraMlContext, - sourceId, + logView, startTime, endTime, dataset, diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts index bb7c615358c0e..aa8876951ee6c 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts @@ -38,14 +38,14 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa fold(throwErrors(Boom.badRequest), identity) ); - const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; + const { startTimestamp, endTimestamp, logView, query, size, highlightTerms } = payload; let entriesPerHighlightTerm; if ('center' in payload) { entriesPerHighlightTerm = await Promise.all( highlightTerms.map((highlightTerm) => - logEntries.getLogEntriesAround(requestContext, sourceId, { + logEntries.getLogEntriesAround(requestContext, logView, { startTimestamp, endTimestamp, query: parseFilterQuery(query), @@ -65,7 +65,7 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa entriesPerHighlightTerm = await Promise.all( highlightTerms.map((highlightTerm) => - logEntries.getLogEntries(requestContext, sourceId, { + logEntries.getLogEntries(requestContext, logView, { startTimestamp, endTimestamp, query: parseFilterQuery(query), diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary.ts b/x-pack/plugins/infra/server/routes/log_entries/summary.ts index 3ff0ded8a7c24..dd48c21a590ae 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary.ts @@ -37,11 +37,11 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke logEntriesSummaryRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; + const { logView, startTimestamp, endTimestamp, bucketSize, query } = payload; const buckets = await logEntries.getLogSummaryBucketsBetween( requestContext, - sourceId, + logView, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts index ca219cac41e2b..206e02bc57278 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts @@ -39,11 +39,11 @@ export const initLogEntriesSummaryHighlightsRoute = ({ logEntriesSummaryHighlightsRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; + const { logView, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( requestContext, - sourceId, + logView, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 27c49032c03f4..0c893171b5b67 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -43,7 +43,10 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { const [, , { logViews }] = await libs.getStartServices(); const logQueryFields: LogQueryFields | undefined = await logViews .getScopedClient(request) - .getResolvedLogView(snapshotRequest.sourceId) + .getResolvedLogView({ + type: 'log-view-reference', + logViewId: snapshotRequest.sourceId, + }) .then( ({ indices }) => ({ indexPattern: indices }), () => undefined diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts index 4b3682db3d1f6..f11a67ecb4a0c 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts @@ -23,7 +23,7 @@ import { applyMetadataToLastPath } from './apply_metadata_to_last_path'; const getMetricValue = (row: MetricsAPIRow) => { if (!isNumber(row.metric_0)) return null; const value = row.metric_0; - return isFinite(value) ? value : null; + return Number.isFinite(value) ? value : null; }; const calculateMax = (rows: MetricsAPIRow[]) => { @@ -31,7 +31,8 @@ const calculateMax = (rows: MetricsAPIRow[]) => { }; const calculateAvg = (rows: MetricsAPIRow[]): number => { - return sum(rows.map(getMetricValue)) / rows.length || 0; + const values = rows.map(getMetricValue).filter(Number.isFinite); + return sum(values) / Math.max(values.length, 1); }; const getLastValue = (rows: MetricsAPIRow[]) => { diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index ae342cfad7b28..890295561e3a7 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -45,7 +45,7 @@ export const transformRequestToMetricsAPIRequest = async ({ ? snapshotRequest.overrideCompositeSize : compositeSize, alignDataToEnd: true, - dropPartialBuckets: true, + dropPartialBuckets: snapshotRequest.dropPartialBuckets ?? true, includeTimeseries: snapshotRequest.includeTimeseries, }; diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts index 99579c5a588c6..bb21053cfe9d8 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts @@ -57,7 +57,7 @@ describe('LogEntries search strategy', () => { logEntriesSearchStrategy.search( { params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, @@ -143,7 +143,7 @@ describe('LogEntries search strategy', () => { { id: requestId, params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, @@ -223,7 +223,7 @@ describe('LogEntries search strategy', () => { { id: logEntriesSearchRequestStateRT.encode({ esRequestId: 'UNKNOWN_ID' }), params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts index 81ef319828be1..f0f5c6304d615 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -72,7 +72,7 @@ export const logEntriesSearchStrategyProvider = ({ const request = decodeOrThrow(asyncRequestRT)(rawRequest); const resolvedLogView$ = defer(() => - logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.sourceId) + logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.logView) ).pipe(take(1), shareReplay(1)); const messageFormattingRules$ = defer(() => diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts index d2d28174490c8..19d5345122374 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts @@ -56,7 +56,10 @@ describe('LogEntry search strategy', () => { const response = await lastValueFrom( logEntrySearchStrategy.search( { - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies @@ -141,7 +144,10 @@ describe('LogEntry search strategy', () => { logEntrySearchStrategy.search( { id: requestId, - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies @@ -193,7 +199,10 @@ describe('LogEntry search strategy', () => { const response = logEntrySearchStrategy.search( { id: logEntrySearchRequestStateRT.encode({ esRequestId: 'UNKNOWN_ID' }), - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index 714e0b792c612..1d558094e351d 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -48,7 +48,7 @@ export const logEntrySearchStrategyProvider = ({ const request = decodeOrThrow(asyncRequestRT)(rawRequest); const resolvedLogView$ = defer(() => - logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.sourceId) + logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.logView) ).pipe(take(1), shareReplay(1)); const recoveredRequest$ = of(request).pipe( diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts b/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts index 5c20297b82c65..5738c94c8aa40 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts +++ b/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts @@ -5,12 +5,15 @@ * 2.0. */ +import { LogViewReference } from '../../../common/log_views'; import { createResolvedLogViewMock } from '../../../common/log_views/resolved_log_view.mock'; import { ILogViewsClient } from './types'; export const createLogViewsClientMock = (): jest.Mocked => ({ getLogView: jest.fn(), - getResolvedLogView: jest.fn((logViewId: string) => Promise.resolve(createResolvedLogViewMock())), + getResolvedLogView: jest.fn((logViewReference: LogViewReference) => + Promise.resolve(createResolvedLogViewMock()) + ), putLogView: jest.fn(), resolveLogView: jest.fn(), }); diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts b/x-pack/plugins/infra/server/services/log_views/log_views_client.ts index 9f43cee871f73..3f832c6770717 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts +++ b/x-pack/plugins/infra/server/services/log_views/log_views_client.ts @@ -19,7 +19,9 @@ import { LogIndexReference, LogView, LogViewAttributes, + LogViewReference, LogViewsStaticConfig, + persistedLogViewReferenceRT, ResolvedLogView, resolveLogView, } from '../../../common/log_views'; @@ -65,8 +67,10 @@ export class LogViewsClient implements ILogViewsClient { ); } - public async getResolvedLogView(logViewId: string): Promise { - const logView = await this.getLogView(logViewId); + public async getResolvedLogView(logViewReference: LogViewReference): Promise { + const logView = persistedLogViewReferenceRT.is(logViewReference) + ? await this.getLogView(logViewReference.logViewId) + : logViewReference; const resolvedLogView = await this.resolveLogView(logView.id, logView.attributes); return resolvedLogView; } diff --git a/x-pack/plugins/infra/server/services/log_views/types.ts b/x-pack/plugins/infra/server/services/log_views/types.ts index 50b4e65cf7548..b5f91cb3587b4 100644 --- a/x-pack/plugins/infra/server/services/log_views/types.ts +++ b/x-pack/plugins/infra/server/services/log_views/types.ts @@ -16,6 +16,7 @@ import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugi import { LogView, LogViewAttributes, + LogViewReference, LogViewsStaticConfig, ResolvedLogView, } from '../../../common/log_views'; @@ -44,7 +45,7 @@ export interface LogViewsServiceStart { export interface ILogViewsClient { getLogView(logViewId: string): Promise; - getResolvedLogView(logViewId: string): Promise; + getResolvedLogView(logView: LogViewReference): Promise; putLogView(logViewId: string, logViewAttributes: Partial): Promise; resolveLogView(logViewId: string, logViewAttributes: LogViewAttributes): Promise; } 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 9501598c53db2..970aaf83b5ae8 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 @@ -187,4 +187,7 @@ type TestSubject = | 'droppableList.addButton' | 'droppableList.input-0' | 'droppableList.input-1' - | 'droppableList.input-2'; + | 'droppableList.input-2' + | 'prefixField.input' + | 'suffixField.input' + | 'patternDefinitionsField'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx new file mode 100644 index 0000000000000..d34e4d1476bb8 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx @@ -0,0 +1,141 @@ +/* + * 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 REDACT_TYPE = 'redact'; + +describe('Processor: Redact', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + let clickAddPattern: () => Promise; + 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 { find, component, actions } = testBed; + + clickAddPattern = async () => { + await act(async () => { + find('droppableList.addButton').simulate('click'); + }); + component.update(); + }; + + 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(REDACT_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 + 'A value is required.', // First input in "Patterns" list + ]); + }); + + test('saves with default parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value + form.setInputValue('fieldNameField.input', 'test_redact_processor'); + + // Add pattern 1 + form.setInputValue('droppableList.input-0', 'pattern1'); + + // Add pattern 2 + await clickAddPattern(); + form.setInputValue('droppableList.input-1', 'pattern2'); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, REDACT_TYPE); + + expect(processors[0][REDACT_TYPE]).toEqual({ + field: 'test_redact_processor', + patterns: ['pattern1', 'pattern2'], + }); + }); + + test('saves with optional parameter values', async () => { + const { + actions: { saveNewProcessor }, + component, + find, + form, + } = testBed; + + // Add "field" value + form.setInputValue('fieldNameField.input', 'test_redact_processor'); + + // Add one pattern to the list + form.setInputValue('droppableList.input-0', 'pattern1'); + + // Set suffix and prefix + form.setInputValue('prefixField.input', '$'); + form.setInputValue('suffixField.input', '$'); + + await act(async () => { + find('patternDefinitionsField').simulate('change', { + jsonContent: JSON.stringify({ GITHUB_NAME: '@%{USERNAME}' }), + }); + + // advance timers to allow the form to validate + jest.advanceTimersByTime(0); + }); + component.update(); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, REDACT_TYPE); + + expect(processors[0][REDACT_TYPE]).toEqual({ + field: 'test_redact_processor', + patterns: ['pattern1'], + suffix: '$', + prefix: '$', + pattern_definitions: { GITHUB_NAME: '@%{USERNAME}' }, + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts index bb63461d71209..e4b9817e89579 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts @@ -8,3 +8,4 @@ export { DragAndDropTextList } from './drag_and_drop_text_list'; export { XJsonEditor } from './xjson_editor'; export { TextEditor } from './text_editor'; +export { InputList } from './input_list'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss new file mode 100644 index 0000000000000..bec0c9981e308 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss @@ -0,0 +1,20 @@ +.pipelineProcessorsEditor__form__inputList { + &__panel { + background-color: $euiColorLightestShade; + padding: $euiSizeM; + } + + &__removeButton { + margin-left: $euiSizeS; + } + + &__item { + background-color: $euiColorLightestShade; + padding-top: $euiSizeS; + padding-bottom: $euiSizeS; + } + + &__labelContainer { + margin-bottom: $euiSizeXS; + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx new file mode 100644 index 0000000000000..602c5598551a5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx @@ -0,0 +1,166 @@ +/* + * 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 React, { useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiFieldText, + EuiFormRow, + EuiText, +} from '@elastic/eui'; + +import { + UseField, + ArrayItem, + ValidationFunc, + getFieldValidityAndErrorMessage, +} from '../../../../../../shared_imports'; + +import './input_list.scss'; + +interface Props { + label: string; + helpText: React.ReactNode; + error: string | null; + value: ArrayItem[]; + onAdd: () => void; + onRemove: (id: number) => void; + addLabel: string; + /** + * Validation to be applied to every text item + */ + textValidations?: Array>; + /** + * Serializer to be applied to every text item + */ + textSerializer?: (v: string) => O; + /** + * Deserializer to be applied to every text item + */ + textDeserializer?: (v: unknown) => string; +} + +const i18nTexts = { + removeItemButtonAriaLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.inputList.removeItemLabel', + { defaultMessage: 'Remove item' } + ), +}; + +export function InputList({ + label, + helpText, + error, + value, + onAdd, + onRemove, + addLabel, + textValidations, + textDeserializer, + textSerializer, +}: Props): JSX.Element { + const [firstItemId] = useState(() => uuidv4()); + + return ( + + <> + + + + + + + + +

    {helpText}

    +
    +
    +
    + +
    + {value.map((item, idx) => ( + + + + path={item.path} + config={{ + validations: textValidations + ? textValidations.map((validator) => ({ validator })) + : undefined, + deserializer: textDeserializer, + serializer: textSerializer, + }} + readDefaultValueOnForm={!item.isNew} + > + {(field) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + return ( + + + + ); + }} + + + + {value.length > 1 ? ( + onRemove(item.id)} + size="s" + /> + ) : ( + + )} + + + ))} + + {addLabel} + +
    + +
    + ); +} 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 e3a0fae36e577..bf9ac7006e1c2 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 @@ -31,6 +31,7 @@ export { Kv } from './kv'; export { Lowercase } from './lowercase'; export { NetworkDirection } from './network_direction'; export { Pipeline } from './pipeline'; +export { Redact } from './redact'; export { RegisteredDomain } from './registered_domain'; export { Remove } from './remove'; export { Rename } from './rename'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx new file mode 100644 index 0000000000000..4e8885e94eb7e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx @@ -0,0 +1,207 @@ +/* + * 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 { + FIELD_TYPES, + Field, + UseField, + UseArray, + fieldValidators, + ValidationFunc, +} from '../../../../../../shared_imports'; + +import { XJsonEditor, InputList } from '../field_components'; + +import { FieldNameField } from './common_fields/field_name_field'; +import { IgnoreMissingField } from './common_fields/ignore_missing_field'; +import { FieldsConfig, to, from, EDITOR_PX_HEIGHT } from './shared'; + +const { isJsonField, emptyField } = fieldValidators; + +const i18nTexts = { + addPatternLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsAddPatternLabel', + { defaultMessage: 'Add pattern' } + ), +}; + +const valueRequiredMessage = i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsValueRequiredError', + { defaultMessage: 'A value is required.' } +); + +const patternsValidation: ValidationFunc = ({ value }) => { + if (typeof value === 'string' && value.length === 0) { + return { + message: valueRequiredMessage, + }; + } +}; + +const patternValidations = [emptyField(valueRequiredMessage)]; + +const fieldsConfig: FieldsConfig = { + /* Required field configs */ + patterns: { + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.patternsFieldLabel', { + defaultMessage: 'Patterns', + }), + deserializer: String, + helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.patternsHelpText', { + defaultMessage: 'A list of grok expressions to match and redact named captures with.', + }), + validations: [ + { + validator: patternsValidation as ValidationFunc, + }, + ], + }, + /* Optional field configs */ + pattern_definitions: { + type: FIELD_TYPES.TEXT, + deserializer: to.jsonString, + serializer: from.optionalJson, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternDefinitionsLabel', + { + defaultMessage: 'Pattern definitions (optional)', + } + ), + helpText: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternDefinitionsHelpText', + { + defaultMessage: + 'A map of pattern-name and pattern tuples defining custom patterns to be used by the processor. Patterns matching existing names will override the pre-existing definition.', + } + ), + validations: [ + { + validator: isJsonField( + i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsDefinitionsInvalidJSONError', + { defaultMessage: 'Invalid JSON' } + ), + { + allowEmptyString: true, + } + ), + }, + ], + }, + + prefix: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.prefixLabel', { + defaultMessage: 'Prefix (optional)', + }), + deserializer: String, + serializer: from.undefinedIfValue(''), + helpText: ( + {'<'} }} + /> + ), + }, + + suffix: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.suffixLabel', { + defaultMessage: 'Suffix (optional)', + }), + deserializer: String, + serializer: from.undefinedIfValue(''), + helpText: ( + {'>'} }} + /> + ), + }, +}; + +export const Redact: FunctionComponent = () => { + return ( + <> + + + + {({ items, addItem, removeItem, error }) => { + return ( + + ); + }} + + + + + + + + + + + + + + + + + + + + ); +}; 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 d47f90abbd36d..cd53afedcc341 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 @@ -37,6 +37,7 @@ import { Lowercase, NetworkDirection, Pipeline, + Redact, RegisteredDomain, Remove, Rename, @@ -577,6 +578,24 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, }), }, + redact: { + FieldsComponent: Redact, + docLinkPath: '/redact-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.redact', { + defaultMessage: 'Redact', + }), + typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.redact', { + defaultMessage: + 'The Redact processor uses the Grok rules engine to obscure text in the input document matching the given Grok patterns.', + }), + getDefaultDescription: ({ field }) => + i18n.translate('xpack.ingestPipelines.processors.defaultDescription.redact', { + defaultMessage: 'Redact values from "{field}" that match a grok pattern', + values: { + field, + }, + }), + }, registered_domain: { FieldsComponent: RegisteredDomain, docLinkPath: '/registered-domain-processor.html', diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 39bc64adef207..2f6d57bc86942 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -55,6 +55,7 @@ import { cellValueTrigger, CELL_VALUE_TRIGGER, type CellValueContext, + shouldFetch$, } from '@kbn/embeddable-plugin/public'; import type { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; @@ -501,20 +502,14 @@ export class Embeddable // Update search context and reload on changes related to search this.inputReloadSubscriptions.push( - this.getUpdated$() - .pipe(map(() => this.getInput())) - .pipe( - distinctUntilChanged((a, b) => - fastIsEqual( - [a.filters, a.query, a.timeRange, a.searchSessionId], - [b.filters, b.query, b.timeRange, b.searchSessionId] - ) - ), - skip(1) - ) - .subscribe(async (input) => { + shouldFetch$(this.getUpdated$(), () => this.getInput()).subscribe( + (input) => { + // reset removable messages + // Dashboard search/context changes are detected here + this.additionalUserMessages = {}; this.onContainerStateChanged(input); - }) + } + ) ); } @@ -596,11 +591,10 @@ export class Embeddable }) ); - const mergedSearchContext = this.getMergedSearchContext(); - if (!this.savedVis) { return userMessages; } + const mergedSearchContext = this.getMergedSearchContext(); const frameDatasourceAPI: FrameDatasourceAPI = { dataViews: { @@ -652,13 +646,9 @@ export class Embeddable } return () => { - const withMessagesRemoved = { - ...this.additionalUserMessages, - }; - - messages.map(({ uniqueId }) => uniqueId).forEach((id) => delete withMessagesRemoved[id]); - - this.additionalUserMessages = withMessagesRemoved; + messages.forEach(({ uniqueId }) => { + delete this.additionalUserMessages[uniqueId]; + }); }; }; @@ -1182,7 +1172,11 @@ export class Embeddable if (!this.savedVis || !this.isInitialized || this.isDestroyed) { return; } - this.handleContainerStateChanged(this.input); + if (this.handleContainerStateChanged(this.input)) { + // reset removable messages + // Unified histogram search/context changes are detected here + this.additionalUserMessages = {}; + } if (this.domNode) { this.render(this.domNode); } diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts index f031b9d1e2882..d6272626618ec 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -43,7 +43,6 @@ export const createEndpointListItemRoute = (router: ListsPluginRouter): void => comments, description, entries, - expire_time: expireTime, item_id: itemId, os_types: osTypes, type, @@ -63,7 +62,6 @@ export const createEndpointListItemRoute = (router: ListsPluginRouter): void => comments, description, entries, - expireTime, itemId, meta, name, diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts index a0a2c8f43f83f..e1e117f6c5604 100644 --- a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts @@ -49,7 +49,6 @@ export const updateEndpointListItemRoute = (router: ListsPluginRouter): void => entries, item_id: itemId, tags, - expire_time: expireTime, } = request.body; const exceptionLists = await getExceptionListClient(context); const exceptionListItem = await exceptionLists.updateEndpointListItem({ @@ -57,7 +56,6 @@ export const updateEndpointListItemRoute = (router: ListsPluginRouter): void => comments, description, entries, - expireTime, id, itemId, meta, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index e4751662a1949..00ecb1a8e32a0 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -65,7 +65,7 @@ export const updateExceptionListItemRoute = (router: ListsPluginRouter): void => }); } else { const exceptionLists = await getExceptionListClient(context); - const exceptionListItem = await exceptionLists.updateExceptionListItem({ + const exceptionListItem = await exceptionLists.updateOverwriteExceptionListItem({ _version, comments, description, diff --git a/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts index 0395c36494d7d..4d4c8a07b455e 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts @@ -83,6 +83,7 @@ export const duplicateExceptionListAndItems = async ({ comments: [], description: item.description, entries: item.entries, + expire_time: item.expire_time, item_id: newItemId, list_id: newlyCreatedList.list_id, meta: item.meta, diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index b8fbb35f29354..db9c62ae5b377 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -97,6 +97,7 @@ import { findExceptionListsItemPointInTimeFinder } from './find_exception_list_i import { findValueListExceptionListItemsPointInTimeFinder } from './find_value_list_exception_list_items_point_in_time_finder'; import { findExceptionListItemPointInTimeFinder } from './find_exception_list_item_point_in_time_finder'; import { duplicateExceptionListAndItems } from './duplicate_exception_list'; +import { updateOverwriteExceptionListItem } from './update_overwrite_exception_list_item'; /** * Class for use for exceptions that are with trusted applications or @@ -287,7 +288,6 @@ export class ExceptionListClient { comments, description, entries, - expireTime, itemId, meta, name, @@ -301,7 +301,7 @@ export class ExceptionListClient { comments, description, entries, - expireTime, + expireTime: undefined, // Not currently used with endpoint exceptions itemId, listId: ENDPOINT_LIST_ID, meta, @@ -358,7 +358,6 @@ export class ExceptionListClient { comments, description, entries, - expireTime, id, itemId, meta, @@ -374,7 +373,7 @@ export class ExceptionListClient { comments, description, entries, - expireTime, + expireTime: undefined, // Not currently used with endpoint exceptions id, itemId, meta, @@ -579,6 +578,11 @@ export class ExceptionListClient { /** * Update an existing exception list item + * + * NOTE: This method will PATCH the targeted exception list item, not fully overwrite it. + * Any undefined fields passed in will not be changed in the existing record. To unset any + * fields use the `updateOverwriteExceptionListItem` method + * * @param options * @param options._version document version * @param options.comments user comments attached to item @@ -647,6 +651,81 @@ export class ExceptionListClient { }); }; + /** + * Update an existing exception list item using the overwrite method in order to behave + * more like a PUT request rather than a PATCH request. + * + * This was done in order to correctly unset types via update which cannot be accomplished + * using the regular `updateExceptionItem` method. All other results of the methods are identical + * + * @param options + * @param options._version document version + * @param options.comments user comments attached to item + * @param options.entries item exception entries logic + * @param options.id the "id" of the exception list item + * @param options.description a description of the exception list + * @param options.itemId the "item_id" of the exception list item + * @param options.meta Optional meta data about the exception list item + * @param options.name the "name" of the exception list + * @param options.namespaceType saved object namespace (single | agnostic) + * @param options.osTypes item os types to apply + * @param options.tags user assigned tags of exception list + * @param options.type container type + * @returns the updated exception list item or null if none exists + */ + public updateOverwriteExceptionListItem = async ({ + _version, + comments, + description, + entries, + expireTime, + id, + itemId, + meta, + name, + namespaceType, + osTypes, + tags, + type, + }: UpdateExceptionListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + let updatedItem: UpdateExceptionListItemOptions = { + _version, + comments, + description, + entries, + expireTime, + id, + itemId, + meta, + name, + namespaceType, + osTypes, + tags, + type, + }; + + if (this.enableServerExtensionPoints) { + updatedItem = await this.serverExtensionsClient.pipeRun( + 'exceptionsListPreUpdateItem', + updatedItem, + this.getServerExtensionCallbackContext(), + (data) => { + return validateData( + updateExceptionListItemSchema, + transformUpdateExceptionListItemOptionsToUpdateExceptionListItemSchema(data) + ); + } + ); + } + + return updateOverwriteExceptionListItem({ + ...updatedItem, + savedObjectsClient, + user, + }); + }; + /** * Delete an exception list item by either id or item_id * @param options diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index b994919398a1c..b93de1413e4db 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -274,8 +274,6 @@ export interface CreateEndpointListItemOptions { comments: CreateCommentsArray; /** The entries of the endpoint list item */ entries: EntriesArray; - /** an optional datetime string with an expiration time */ - expireTime: ExpireTimeOrUndefined; /** The item id of the list item */ itemId: ItemId; /** The name of the list item */ @@ -347,8 +345,6 @@ export interface UpdateEndpointListItemOptions { comments: UpdateCommentsArray; /** The entries of the endpoint list item */ entries: EntriesArray; - /** an optional datetime string with an expiration time */ - expireTime: ExpireTimeOrUndefined; /** The id of the list item (Either this or itemId has to be defined) */ id: IdOrUndefined; /** The item id of the list item (Either this or id has to be defined) */ diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts index e3fcb9d2461e4..4a680c7d2fb06 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -32,7 +32,7 @@ import { } from './utils'; import { getExceptionListItem } from './get_exception_list_item'; -interface UpdateExceptionListItemOptions { +export interface UpdateExceptionListItemOptions { id: IdOrUndefined; comments: UpdateCommentsArrayOrUndefined; _version: _VersionOrUndefined; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_overwrite_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_overwrite_exception_list_item.ts new file mode 100644 index 0000000000000..ef1d470bf67a0 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/update_overwrite_exception_list_item.ts @@ -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 type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; + +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; + +import { + transformSavedObjectUpdateToExceptionListItem, + transformUpdateCommentsToComments, +} from './utils'; +import { getExceptionListItem } from './get_exception_list_item'; +import { UpdateExceptionListItemOptions } from './update_exception_list_item'; + +export const updateOverwriteExceptionListItem = async ({ + _version, + comments, + entries, + expireTime, + id, + savedObjectsClient, + namespaceType, + name, + osTypes, + description, + itemId, + meta, + user, + tags, + type, +}: UpdateExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionListItem = await getExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + if (exceptionListItem == null) { + return null; + } else { + const transformedComments = transformUpdateCommentsToComments({ + comments, + existingComments: exceptionListItem.comments, + user, + }); + const savedObject = await savedObjectsClient.create( + savedObjectType, + { + comments: transformedComments, + created_at: exceptionListItem.created_at, + created_by: exceptionListItem.created_by, + description: description ?? exceptionListItem.description, + entries, + expire_time: expireTime, + immutable: undefined, + item_id: itemId, + list_id: exceptionListItem.list_id, + list_type: 'item', + meta, + name: name ?? exceptionListItem.name, + os_types: osTypes, + tags: tags ?? exceptionListItem.tags, + tie_breaker_id: exceptionListItem.tie_breaker_id, + type: type ?? exceptionListItem.type, + updated_by: user, + version: exceptionListItem._version ? parseInt(exceptionListItem._version, 10) : undefined, + }, + { + id, + overwrite: true, + version: _version, + } + ); + return transformSavedObjectUpdateToExceptionListItem({ + exceptionListItem, + savedObject, + }); + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/index.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/index.ts index 8af2bfcfa7175..362d7e94072c1 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/index.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/index.ts @@ -228,7 +228,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({ created_by: exceptionListItem.created_by, description: description ?? exceptionListItem.description, entries: entries ?? exceptionListItem.entries, - expire_time: expireTime ?? exceptionListItem.expire_time, + expire_time: expireTime, id, item_id: exceptionListItem.item_id, list_id: exceptionListItem.list_id, diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 639c8fe5a20db..ab98b69a79f0b 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -155,12 +155,13 @@ export class MbMap extends Component { async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise { this._reportUsage(); + const glyphsUrlTemplate = await getGlyphUrl(); return new Promise((resolve) => { const mbStyle = { version: 8 as 8, sources: {}, layers: [], - glyphs: getGlyphUrl(), + glyphs: glyphsUrlTemplate, }; const options: MapOptions = { diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx new file mode 100644 index 0000000000000..bcb5aea3cca85 --- /dev/null +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx @@ -0,0 +1,290 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import { getControlledBy, MapEmbeddable } from './map_embeddable'; +import { buildExistsFilter, disableFilter, pinFilter, toggleFilterNegated } from '@kbn/es-query'; +import type { DataViewFieldBase, DataViewBase } from '@kbn/es-query'; +import { MapEmbeddableConfig, MapEmbeddableInput } from './types'; +import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; + +jest.mock('../kibana_services', () => { + return { + getHttp() { + return { + basePath: { + prepend: (url: string) => url, + }, + }; + }, + getMapsCapabilities() { + return { save: true }; + }, + getSearchService() { + return { + session: { + getSearchOptions() { + return undefined; + }, + }, + }; + }, + getShowMapsInspectorAdapter() { + return false; + }, + getTimeFilter() { + return { + getTime() { + return { from: 'now-7d', to: 'now' }; + }, + }; + }, + }; +}); + +jest.mock('../connected_components/map_container', () => { + return { + MapContainer: () => { + return
    mockLayerTOC
    ; + }, + }; +}); + +jest.mock('../routes/map_page', () => { + class MockSavedMap { + // eslint-disable-next-line @typescript-eslint/no-var-requires + private _store = require('../reducers/store').createMapStore(); + private _attributes: MapSavedObjectAttributes = { + title: 'myMap', + }; + + whenReady = async function () {}; + + getStore() { + return this._store; + } + getAttributes() { + return this._attributes; + } + getAutoFitToBounds() { + return true; + } + getSharingSavedObjectProps() { + return null; + } + } + return { SavedMap: MockSavedMap }; +}); + +function untilInitialized(mapEmbeddable: MapEmbeddable): Promise { + return new Promise((resolve) => { + // @ts-expect-error setInitializationFinished is protected but we are overriding it to know when embeddable is initialized + mapEmbeddable.setInitializationFinished = () => { + resolve(); + }; + }); +} + +function onNextTick(): Promise { + // wait one tick to give observables time to fire + return new Promise((resolve) => setTimeout(resolve, 0)); +} + +describe('shouldFetch$', () => { + test('should not fetch when search context does not change', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + title: 'updated map title', + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + describe('on filters change', () => { + test('should fetch on filter change', async () => { + const existsFilter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [existsFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(existsFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).toHaveBeenCalled(); + }); + + test('should not fetch on disabled filter change', async () => { + const disabledFilter = disableFilter( + buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ) + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [disabledFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(disabledFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + test('should not fetch when unpinned filter is pinned', async () => { + const unpinnedFilter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [unpinnedFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [pinFilter(unpinnedFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + test('should not fetch on filter controlled by map embeddable change', async () => { + const embeddableId = 'map1'; + const filter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const controlledByFilter = { + ...filter, + meta: { + ...filter.meta, + controlledBy: getControlledBy(embeddableId), + }, + }; + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: embeddableId, + filters: [controlledByFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(controlledByFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + }); + + describe('on searchSessionId change', () => { + test('should fetch when filterByMapExtent is false', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filterByMapExtent: false, + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + searchSessionId: uuidv4(), + }); + + await onNextTick(); + + expect(fetchSpy).toHaveBeenCalled(); + }); + + test('should not fetch when filterByMapExtent is true', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filterByMapExtent: true, + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + searchSessionId: uuidv4(), + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 51b806cec5dce..41579d4f5375d 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -14,7 +14,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Subscription } from 'rxjs'; import { Unsubscribe } from 'redux'; import { EuiEmptyPrompt } from '@elastic/eui'; -import { type Filter, compareFilters, type TimeRange, type Query } from '@kbn/es-query'; +import { type Filter } from '@kbn/es-query'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { Embeddable, @@ -24,6 +24,7 @@ import { VALUE_CLICK_TRIGGER, omitGenericEmbeddableInput, FilterableEmbeddable, + shouldFetch$, } from '@kbn/embeddable-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; @@ -104,6 +105,10 @@ function getIsRestore(searchSessionId?: string) { return searchSessionOptions ? searchSessionOptions.isRestore : false; } +export function getControlledBy(id: string) { + return `mapEmbeddablePanel${id}`; +} + export class MapEmbeddable extends Embeddable implements ReferenceOrValueEmbeddable, FilterableEmbeddable @@ -114,15 +119,10 @@ export class MapEmbeddable private _isActive: boolean; private _savedMap: SavedMap; private _renderTooltipContent?: RenderToolTipContent; - private _subscription: Subscription; + private _subscriptions: Subscription[] = []; private _prevIsRestore: boolean = false; private _prevMapExtent?: MapExtent; - private _prevTimeRange?: TimeRange; - private _prevTimeslice?: [number, number]; - private _prevQuery?: Query; - private _prevFilters: Filter[] = []; private _prevSyncColors?: boolean; - private _prevSearchSessionId?: string; private _domNode?: HTMLElement; private _unsubscribeFromStore?: Unsubscribe; private _isInitialized = false; @@ -145,8 +145,8 @@ export class MapEmbeddable this._isActive = true; this._savedMap = new SavedMap({ mapEmbeddableInput: initialInput }); this._initializeSaveMap(); - this._subscription = this.getUpdated$().subscribe(() => this.onUpdate()); - this._controlledBy = `mapEmbeddablePanel${this.id}`; + this._subscriptions.push(this.getUpdated$().subscribe(() => this.onUpdate())); + this._controlledBy = getControlledBy(this.id); } public reportsEmbeddableLoad() { @@ -193,9 +193,20 @@ export class MapEmbeddable // Passing callback into redux store instead of regular pattern of getting redux state changes for performance reasons store.dispatch(setOnMapMove(this._propogateMapMovement)); - this._dispatchSetQuery({ - forceRefresh: false, - }); + this._dispatchSetQuery({ forceRefresh: false }); + this._subscriptions.push( + shouldFetch$(this.getUpdated$(), () => { + return { + ...this.getInput(), + filters: this._getInputFilters(), + searchSessionId: this._getSearchSessionId(), + }; + }).subscribe(() => { + this._dispatchSetQuery({ + forceRefresh: false, + }); + }) + ); const mapStateJSON = this._savedMap.getAttributes().mapStateJSON; if (mapStateJSON) { @@ -309,18 +320,6 @@ export class MapEmbeddable } onUpdate() { - if ( - !_.isEqual(this.input.timeRange, this._prevTimeRange) || - !_.isEqual(this.input.timeslice, this._prevTimeslice) || - !_.isEqual(this.input.query, this._prevQuery) || - !compareFilters(this._getFilters(), this._prevFilters) || - this._getSearchSessionId() !== this._prevSearchSessionId - ) { - this._dispatchSetQuery({ - forceRefresh: false, - }); - } - if (this.input.syncColors !== this._prevSyncColors) { this._dispatchSetChartsPaletteServiceGetColor(this.input.syncColors); } @@ -382,7 +381,7 @@ export class MapEmbeddable } }; - _getFilters() { + _getInputFilters() { return this.input.filters ? this.input.filters.filter( (filter) => !filter.meta.disabled && filter.meta.controlledBy !== this._controlledBy @@ -401,15 +400,9 @@ export class MapEmbeddable } _dispatchSetQuery({ forceRefresh }: { forceRefresh: boolean }) { - const filters = this._getFilters(); - this._prevTimeRange = this.input.timeRange; - this._prevTimeslice = this.input.timeslice; - this._prevQuery = this.input.query; - this._prevFilters = filters; - this._prevSearchSessionId = this._getSearchSessionId(); this._savedMap.getStore().dispatch( setQuery({ - filters, + filters: this._getInputFilters(), query: this.input.query, timeFilters: this.input.timeRange, timeslice: this.input.timeslice @@ -675,9 +668,9 @@ export class MapEmbeddable unmountComponentAtNode(this._domNode); } - if (this._subscription) { - this._subscription.unsubscribe(); - } + this._subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); } reload() { diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index d024eee88ae6d..1d29a23ec44c5 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Filter } from '@kbn/es-query'; import type { DataView } from '@kbn/data-plugin/common'; import { Embeddable, @@ -13,7 +12,7 @@ import { EmbeddableOutput, SavedObjectEmbeddableInput, } from '@kbn/embeddable-plugin/public'; -import type { Query, TimeRange } from '@kbn/es-query'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { MapCenterAndZoom, MapExtent, MapSettings } from '../../common/descriptor_types'; import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; diff --git a/x-pack/plugins/maps/public/util.test.js b/x-pack/plugins/maps/public/util.test.js index 7fc88578b378a..48498f87fe7c0 100644 --- a/x-pack/plugins/maps/public/util.test.js +++ b/x-pack/plugins/maps/public/util.test.js @@ -5,60 +5,88 @@ * 2.0. */ -import { getGlyphUrl, makePublicExecutionContext } from './util'; - -const MOCK_EMS_SETTINGS = { - isEMSEnabled: () => true, -}; +import { + getGlyphUrl, + makePublicExecutionContext, + testOnlyClearCanAccessEmsFontsPromise, +} from './util'; describe('getGlyphUrl', () => { describe('EMS enabled', () => { - beforeAll(() => { + beforeEach(() => { require('./kibana_services').getHttp = () => ({ basePath: { - prepend: (url) => url, // No need to actually prepend a dev basepath for test + prepend: (path) => `abc${path}`, }, }); + testOnlyClearCanAccessEmsFontsPromise(); }); - describe('EMS proxy disabled', () => { + describe('offline', () => { beforeAll(() => { require('./kibana_services').getEMSSettings = () => { return { getEMSFontLibraryUrl() { - return 'foobar'; + return 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; }, isEMSEnabled() { return true; }, }; }; + require('node-fetch').default = () => { + throw new Error('Simulated offline environment with no EMS access'); + }; }); - test('should return EMS fonts URL', async () => { - expect(getGlyphUrl()).toBe('foobar'); + test('should return kibana fonts template URL', async () => { + expect(await getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + }); + }); + + describe('online', () => { + beforeAll(() => { + require('./kibana_services').getEMSSettings = () => { + return { + getEMSFontLibraryUrl() { + return 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; + }, + isEMSEnabled() { + return true; + }, + }; + }; + require('node-fetch').default = () => { + return Promise.resolve({ status: 200 }); + }; + }); + + test('should return EMS fonts template URL', async () => { + expect(await getGlyphUrl()).toBe( + 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' + ); }); }); }); describe('EMS disabled', () => { beforeAll(() => { - const mockHttp = { - basePath: { - prepend: (path) => `abc${path}`, - }, + require('./kibana_services').getHttp = () => { + return { + basePath: { + prepend: (path) => `abc${path}`, + }, + }; }; - require('./kibana_services').getHttp = () => mockHttp; require('./kibana_services').getEMSSettings = () => { return { - ...MOCK_EMS_SETTINGS, isEMSEnabled: () => false, }; }; }); - test('should return kibana fonts URL', async () => { - expect(getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + test('should return kibana fonts template URL', async () => { + expect(await getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); }); }); }); diff --git a/x-pack/plugins/maps/public/util.ts b/x-pack/plugins/maps/public/util.ts index d63072b163599..3243c8a95cf74 100644 --- a/x-pack/plugins/maps/public/util.ts +++ b/x-pack/plugins/maps/public/util.ts @@ -5,6 +5,7 @@ * 2.0. */ +import fetch from 'node-fetch'; import { EMSClient, FileLayer, TMSService } from '@elastic/ems-client'; import type { KibanaExecutionContext } from '@kbn/core/public'; import { FONTS_API_PATH } from '../common/constants'; @@ -60,9 +61,46 @@ async function getEMSClient(): Promise { return emsClient; } -export function getGlyphUrl(): string { +let canAccessEmsFontsPromise: Promise | null = null; +async function canAccessEmsFonts(): Promise { + if (!canAccessEmsFontsPromise) { + canAccessEmsFontsPromise = new Promise(async (resolve) => { + try { + const emsSettings = getEMSSettings(); + if (!emsSettings!.isEMSEnabled()) { + resolve(false); + } + const emsFontUrlTemplate = emsSettings!.getEMSFontLibraryUrl(); + + const emsFontUrl = emsFontUrlTemplate + .replace('{fontstack}', 'Open Sans') + .replace('{range}', '0-255'); + const resp = await fetch(emsFontUrl, { + method: 'HEAD', + }); + if (resp.status >= 400) { + throw new Error(`status: ${resp.status}`); + } + resolve(true); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + `Unable to access fonts from Elastic Maps Service (EMS). Set kibana.yml 'map.includeElasticMapsService: false' to avoid unnecessary EMS requests.` + ); + resolve(false); + } + }); + } + return canAccessEmsFontsPromise; +} +// test only function to reset singleton for different test cases. +export function testOnlyClearCanAccessEmsFontsPromise() { + canAccessEmsFontsPromise = null; +} + +export async function getGlyphUrl(): Promise { const emsSettings = getEMSSettings(); - if (!emsSettings!.isEMSEnabled()) { + if (!emsSettings!.isEMSEnabled() || !(await canAccessEmsFonts())) { return getHttp().basePath.prepend(`/${FONTS_API_PATH}/{fontstack}/{range}`); } diff --git a/x-pack/plugins/maps/server/kibana_server_services.ts b/x-pack/plugins/maps/server/kibana_server_services.ts index 84cedeb721824..ef12c3edaa81f 100644 --- a/x-pack/plugins/maps/server/kibana_server_services.ts +++ b/x-pack/plugins/maps/server/kibana_server_services.ts @@ -6,19 +6,12 @@ */ import { CoreStart } from '@kbn/core/server'; -import { StartDeps } from './types'; let coreStart: CoreStart; -let pluginsStart: StartDeps; -export function setStartServices(core: CoreStart, plugins: StartDeps) { +export function setStartServices(core: CoreStart) { coreStart = core; - pluginsStart = plugins; } export const getSavedObjectClient = (extraTypes?: string[]) => { return coreStart.savedObjects.createInternalRepository(extraTypes); }; - -export const getIndexPatternsServiceFactory = () => - pluginsStart.data.indexPatterns.dataViewsServiceFactory; -export const getElasticsearch = () => coreStart.elasticsearch; diff --git a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts index 9f2e520c428a2..77d1112f89817 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts @@ -18,10 +18,6 @@ export function registerMapsUsageCollector(usageCollection?: UsageCollectionSetu isReady: () => true, fetch: async () => await getMapsTelemetry(), schema: { - indexPatternsWithGeoFieldCount: { type: 'long' }, - indexPatternsWithGeoPointFieldCount: { type: 'long' }, - indexPatternsWithGeoShapeFieldCount: { type: 'long' }, - geoShapeAggLayersCount: { type: 'long' }, mapsTotalCount: { type: 'long' }, timeCaptured: { type: 'date' }, layerTypes: { diff --git a/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts b/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts index acc9d11c66d4a..64215045bd3eb 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts @@ -29,7 +29,7 @@ function getMockSavedObjectsClient(perPage: number) { } as unknown as ISavedObjectsRepository; } -test('should process all map saved objects with single page', async () => { +test('should process all map saved objects with a single page', async () => { const foundMapIds: string[] = []; await findMaps(getMockSavedObjectsClient(20), async (savedObject) => { foundMapIds.push(savedObject.id); @@ -43,7 +43,7 @@ test('should process all map saved objects with single page', async () => { ]); }); -test('should process all map saved objects with with paging', async () => { +test('should process all map saved objects with paging', async () => { const foundMapIds: string[] = []; await findMaps(getMockSavedObjectsClient(2), async (savedObject) => { foundMapIds.push(savedObject.id); diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts b/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts deleted file mode 100644 index 9877c29bc5951..0000000000000 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { asyncForEach } from '@kbn/std'; -// @ts-ignore -import mapSavedObjects from '../../../common/telemetry/test_resources/sample_map_saved_objects.json'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { IndexPatternStatsCollector } from './index_pattern_stats_collector'; - -test('returns zeroed telemetry data when there are no saved objects', async () => { - const mockIndexPatternService = { - getIds: () => { - return []; - }, - } as unknown as DataViewsService; - const statsCollector = new IndexPatternStatsCollector(mockIndexPatternService); - const stats = await statsCollector.getStats(); - expect(stats).toEqual({ - geoShapeAggLayersCount: 0, - indexPatternsWithGeoFieldCount: 0, - indexPatternsWithGeoPointFieldCount: 0, - indexPatternsWithGeoShapeFieldCount: 0, - }); -}); - -test('returns expected telemetry data from saved objects', async () => { - const mockIndexPatternService = { - get: (id: string) => { - if (id === 'd3d7af60-4c81-11e8-b3d7-01146121b73d') { - return { - getFieldByName: (name: string) => { - return { type: 'geo_point' }; - }, - fields: { - getByType: (type: string) => { - return type === 'geo_point' ? [{}] : []; - }, - }, - }; - } - - if (id === '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4') { - return { - getFieldByName: (name: string) => { - return { type: 'geo_shape' }; - }, - fields: { - getByType: (type: string) => { - return type === 'geo_shape' ? [{}] : []; - }, - }, - }; - } - - if (id === 'indexPatternWithNoGeoFields') { - return { - getFieldByName: (name: string) => { - return null; - }, - fields: { - getByType: (type: string) => { - return []; - }, - }, - }; - } - - throw new Error('Index pattern not found'); - }, - getIds: () => { - return [ - 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4', - 'indexPatternWithNoGeoFields', - 'missingIndexPattern', - ]; - }, - } as unknown as DataViewsService; - const statsCollector = new IndexPatternStatsCollector(mockIndexPatternService); - await asyncForEach(mapSavedObjects, async (savedObject) => { - await statsCollector.push(savedObject); - }); - const stats = await statsCollector.getStats(); - expect(stats).toEqual({ - geoShapeAggLayersCount: 2, // index pattern '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4' with geo_shape field is used in 2 maps with geo_tile_grid aggregation - indexPatternsWithGeoFieldCount: 2, - indexPatternsWithGeoPointFieldCount: 1, - indexPatternsWithGeoShapeFieldCount: 1, - }); -}); diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.ts b/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.ts deleted file mode 100644 index 2b8047cdaf41f..0000000000000 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SavedObject } from '@kbn/core/server'; -import { asyncForEach } from '@kbn/std'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { SCALING_TYPES, SOURCE_TYPES } from '../../../common/constants'; -import { injectReferences } from '../../../common/migrations/references'; -import { - ESGeoGridSourceDescriptor, - ESSearchSourceDescriptor, - LayerDescriptor, -} from '../../../common/descriptor_types'; -import type { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; -import { IndexPatternStats } from './types'; - -/* - * Use IndexPatternStatsCollector instance to track index pattern geospatial field stats. - */ -export class IndexPatternStatsCollector { - private _geoShapeAggCount = 0; - private _indexPatternsService: DataViewsService; - - constructor(indexPatternService: DataViewsService) { - this._indexPatternsService = indexPatternService; - } - - async push(savedObject: SavedObject) { - let layerList: LayerDescriptor[] = []; - try { - const { attributes } = injectReferences(savedObject); - if (!attributes.layerListJSON) { - return; - } - layerList = JSON.parse(attributes.layerListJSON); - } catch (e) { - return; - } - - let geoShapeAggCountPerMap = 0; - await asyncForEach(layerList, async (layerDescriptor) => { - if (await this._isGeoShapeAggLayer(layerDescriptor)) { - geoShapeAggCountPerMap++; - } - }); - this._geoShapeAggCount += geoShapeAggCountPerMap; - } - - async getStats(): Promise { - let geoCount = 0; - let pointCount = 0; - let shapeCount = 0; - - const indexPatternIds = await this._indexPatternsService.getIds(); - await asyncForEach(indexPatternIds, async (indexPatternId) => { - let indexPattern; - try { - indexPattern = await this._indexPatternsService.get(indexPatternId); - } catch (e) { - return; - } - const pointFields = indexPattern.fields.getByType(KBN_FIELD_TYPES.GEO_POINT); - const shapeFields = indexPattern.fields.getByType(KBN_FIELD_TYPES.GEO_SHAPE); - if (pointFields.length || shapeFields.length) { - geoCount++; - } - if (pointFields.length) { - pointCount++; - } - if (shapeFields.length) { - shapeCount++; - } - }); - - return { - // Tracks whether user uses Gold+ functionality of aggregating on geo_shape field - geoShapeAggLayersCount: this._geoShapeAggCount, - indexPatternsWithGeoFieldCount: geoCount, - indexPatternsWithGeoPointFieldCount: pointCount, - indexPatternsWithGeoShapeFieldCount: shapeCount, - }; - } - - async _isFieldGeoShape(indexPatternId: string, geoField: string | undefined): Promise { - if (!geoField || !indexPatternId) { - return false; - } - - let indexPattern; - try { - indexPattern = await this._indexPatternsService.get(indexPatternId); - } catch (e) { - return false; - } - - const field = indexPattern.getFieldByName(geoField); - return !!field && field.type === KBN_FIELD_TYPES.GEO_SHAPE; - } - - async _isGeoShapeAggLayer(layer: LayerDescriptor): Promise { - if (!layer.sourceDescriptor) { - return false; - } - - const sourceDescriptor = layer.sourceDescriptor; - if (sourceDescriptor.type === SOURCE_TYPES.ES_GEO_GRID) { - return await this._isFieldGeoShape( - (sourceDescriptor as ESGeoGridSourceDescriptor).indexPatternId, - (sourceDescriptor as ESGeoGridSourceDescriptor).geoField - ); - } - - if ( - sourceDescriptor.type === SOURCE_TYPES.ES_SEARCH && - (sourceDescriptor as ESSearchSourceDescriptor).scalingType === SCALING_TYPES.CLUSTERS - ) { - return await this._isFieldGeoShape( - (sourceDescriptor as ESSearchSourceDescriptor).indexPatternId, - (sourceDescriptor as ESSearchSourceDescriptor).geoField - ); - } - - return false; - } -} diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index b7497c010f15f..462f8ca57d692 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -5,37 +5,19 @@ * 2.0. */ -import { SavedObjectsClient } from '@kbn/core/server'; -import { - getElasticsearch, - getIndexPatternsServiceFactory, - getSavedObjectClient, -} from '../kibana_server_services'; +import { getSavedObjectClient } from '../kibana_server_services'; import { MapStats, MapStatsCollector } from './map_stats'; -import { IndexPatternStats, IndexPatternStatsCollector } from './index_pattern_stats'; import { findMaps } from './find_maps'; -export type MapsUsage = MapStats & IndexPatternStats; - -async function getReadOnlyIndexPatternsService() { - const factory = getIndexPatternsServiceFactory(); - return factory( - new SavedObjectsClient(getSavedObjectClient()), - getElasticsearch().client.asInternalUser - ); -} +export type MapsUsage = MapStats; export async function getMapsTelemetry(): Promise { const mapStatsCollector = new MapStatsCollector(); - const indexPatternService = await getReadOnlyIndexPatternsService(); - const indexPatternStatsCollector = new IndexPatternStatsCollector(indexPatternService); await findMaps(getSavedObjectClient(), async (savedObject) => { mapStatsCollector.push(savedObject.attributes); - await indexPatternStatsCollector.push(savedObject); }); return { - ...(await indexPatternStatsCollector.getStats()), ...mapStatsCollector.getStats(), }; } diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 0fe317beef05e..1dc497a87fdd9 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -207,6 +207,6 @@ export class MapsPlugin implements Plugin { } start(core: CoreStart, plugins: StartDeps) { - setStartServices(core, plugins); + setStartServices(core); } } diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 0505ffb7473c1..8d32c7229a4e0 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -62,7 +62,6 @@ "@kbn/safer-lodash-set", "@kbn/custom-integrations-plugin", "@kbn/config-schema", - "@kbn/field-types", "@kbn/controls-plugin", "@kbn/shared-ux-router", ], diff --git a/x-pack/plugins/ml/public/application/_app.scss b/x-pack/plugins/ml/public/application/_app.scss deleted file mode 100644 index 6dfae7a8921f3..0000000000000 --- a/x-pack/plugins/ml/public/application/_app.scss +++ /dev/null @@ -1,30 +0,0 @@ -// ML has app specific coloring for it's various warning levels. -// These are used almost everywhere. - -.ml-icon-severity-critical, -.ml-icon-severity-major, -.ml-icon-severity-minor, -.ml-icon-severity-warning, -.ml-icon-severity-unknown { - text-shadow: 1px 1px 1px $euiColorLightShade; -} - -.ml-icon-severity-critical { - color: $mlColorCriticalText; -} - -.ml-icon-severity-major { - color: $mlColorMajorText; -} - -.ml-icon-severity-minor { - color: $mlColorMinorText; -} - -.ml-icon-severity-warning { - color: $mlColorWarningText; -} - -.ml-icon-severity-unknown { - color: $mlColorUnknownText; -} diff --git a/x-pack/plugins/ml/public/application/_hacks.scss b/x-pack/plugins/ml/public/application/_hacks.scss deleted file mode 100644 index 13fabcb8045aa..0000000000000 --- a/x-pack/plugins/ml/public/application/_hacks.scss +++ /dev/null @@ -1,33 +0,0 @@ -.tab-datavisualizer_index_select, -.tab-timeseriesexplorer, -.tab-explorer { - // Make all page background white until More of the pages use EuiPage to wrap in panel-like components - background-color: $euiColorEmptyShade; -} - -// ML specific bootstrap hacks -.button-wrapper { - display: inline; -} - -.button-wrapper.disabled .kuiButton[disabled] { - pointer-events: none; -} - -.button-wrapper.disabled { - cursor: not-allowed; -} - -// SASSTODO: Remove all the floats -.clear, .clearfix { - clear: both; -} - -// Helper class for functional tests to disable anti-aliasing for canvas elements -.mlDisableAntiAliasing { - -webkit-font-smoothing : none; - - * canvas { - image-rendering: pixelated; - } -} diff --git a/x-pack/plugins/ml/public/application/_index.scss b/x-pack/plugins/ml/public/application/_index.scss index 309eea83dd527..ac9e16e5f3e78 100644 --- a/x-pack/plugins/ml/public/application/_index.scss +++ b/x-pack/plugins/ml/public/application/_index.scss @@ -4,30 +4,18 @@ // Protect the rest of Kibana from ML generic namespacing // SASSTODO: Prefix ml selectors instead .ml-app { - // App level - @import 'app'; - // Sub applications @import 'data_frame_analytics/index'; @import 'explorer/index'; // SASSTODO: This file needs to be rewritten - @import 'jobs/index'; // SASSTODO: This collection of sass files has multiple problems - @import 'overview/index'; @import 'timeseriesexplorer/index'; // Components @import 'components/annotations/annotation_description_list/index'; // SASSTODO: This file overwrites EUI directly @import 'components/anomalies_table/index'; // SASSTODO: This file overwrites EUI directly @import 'components/color_range_legend/index'; - @import 'components/controls/index'; @import 'components/entity_cell/index'; @import 'components/influencers_list/index'; - @import 'components/items_grid/index'; @import 'components/job_selector/index'; - @import 'components/loading_indicator/index'; // SASSTODO: This component should be replaced with EuiLoadingSpinner @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly - @import 'components/stats_bar/index'; - @import 'components/ml_embedded_map/index'; - // Hacks are last so they can overwrite anything above if needed - @import 'hacks'; } diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss index 06dfacf63a213..4f11115e82712 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss @@ -1,45 +1,5 @@ // SASSTODO: This file has several direct EUI overwrites that need to be removed .ml-anomalies-table { - .ml-icon-severity-critical, - .ml-icon-severity-major, - .ml-icon-severity-minor, - .ml-icon-severity-warning, - .ml-icon-severity-unknown { - color: inherit; - text-shadow: none; - } - - // SASSTODO: Should only be three options, logic moved to the JS, where EuiIcon accepts a color - .ml-icon-severity-critical { - .euiIcon { - fill: $mlColorCriticalText; - } - } - - .ml-icon-severity-major { - .euiIcon { - fill: $mlColorMajorText; - } - } - - .ml-icon-severity-minor { - .euiIcon { - fill: $mlColorMinorText; - } - } - - .ml-icon-severity-warning { - .euiIcon { - fill: $mlColorWarningText; - } - } - - .ml-icon-severity-unknown { - .euiIcon { - fill: $mlColorUnknownText; - } - } - tr th:first-child, tr td:first-child { width: $euiSizeXL; diff --git a/x-pack/plugins/ml/public/application/components/controls/_controls.scss b/x-pack/plugins/ml/public/application/components/controls/_controls.scss deleted file mode 100644 index d491e88dffa24..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/_controls.scss +++ /dev/null @@ -1,16 +0,0 @@ -.ml-table-controls { - label { - font-size: $euiFontSizeXS; - padding: 0 0 $euiSizeXS $euiSizeXS; - } - - .ml-table-controls-element { - display: inline-block; - padding-left: $euiSize; - } - - select { - font-size: $euiFontSizeXS; - font-style: normal; - } -} diff --git a/x-pack/plugins/ml/public/application/components/controls/_index.scss b/x-pack/plugins/ml/public/application/components/controls/_index.scss deleted file mode 100644 index 84c0e21734b89..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'controls'; -@import 'select_severity/index'; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss b/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss deleted file mode 100644 index f238b65c9b955..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'select_severity'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss b/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss deleted file mode 100644 index 2edfe612183d0..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss +++ /dev/null @@ -1,6 +0,0 @@ -// SASSTODO: Should be removed -.ml-select-severity { - .euiFormControlLayoutClearButton { - display: none; - } -} diff --git a/x-pack/plugins/ml/public/application/components/items_grid/_index.scss b/x-pack/plugins/ml/public/application/components/items_grid/_index.scss deleted file mode 100644 index 243c80da52918..0000000000000 --- a/x-pack/plugins/ml/public/application/components/items_grid/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'items_grid'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss b/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss deleted file mode 100644 index c3bd6b115bbd6..0000000000000 --- a/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss +++ /dev/null @@ -1,3 +0,0 @@ -.ml-items-grid-page-size-menu { - width: 140px; // SASSTODO: Needs a proper calc -} diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index d78f311475501..41b005c7da58b 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -137,7 +137,6 @@ export const JobMessages: FC = ({ <> = ({ height, label }) => { height = height ? +height : 100; return ( -
    - - {label && ( - <> - -
    {label}
    - - )} -
    + + +
    + + {label && ( + <> + +
    {label}
    + + )} +
    +
    +
    ); }; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss deleted file mode 100644 index 6d0d30dae670e..0000000000000 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss deleted file mode 100644 index 495fc40ddb27c..0000000000000 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss +++ /dev/null @@ -1,8 +0,0 @@ -.mlEmbeddedMapContent { - width: 100%; - height: 100%; - display: flex; - flex: 1 1 100%; - z-index: 1; - min-height: 0; // Absolute must for Firefox to scroll contents -} diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx index ae9dbac3c2d02..57d2abe3577b9 100644 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx @@ -143,7 +143,14 @@ export function MlEmbeddedMapComponent({ return (
    ); diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss deleted file mode 100644 index e8d8e85763eff..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'stat'; -@import 'stats_bar'; diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss deleted file mode 100644 index ea570c24f0d7b..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss +++ /dev/null @@ -1,3 +0,0 @@ -.stat { - margin-right: $euiSizeS; -} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss deleted file mode 100644 index c433b53789573..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss +++ /dev/null @@ -1,6 +0,0 @@ -.mlStatsBar { - // SASSTODO: proper calcs - height: 42px; - padding: 14px; - background-color: $euiColorLightestShade; -} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx index ae04a7a3b2448..2a0e8aad89bf0 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx @@ -6,6 +6,7 @@ */ import React, { FC } from 'react'; +import { useEuiTheme } from '@elastic/eui'; export interface StatsBarStat { label: string; @@ -18,8 +19,9 @@ interface StatProps { } export const Stat: FC = ({ stat }) => { + const { euiTheme } = useEuiTheme(); return ( - + {stat.label}:{' '} {stat.value} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx index 15324c180e293..535f5708693b0 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx @@ -6,6 +6,7 @@ */ import React, { FC } from 'react'; +import { useEuiTheme } from '@elastic/eui'; import { Stat, StatsBarStat } from './stat'; interface Stats { @@ -37,9 +38,13 @@ interface StatsBarProps { } export const StatsBar: FC = ({ stats, dataTestSub }) => { + const { euiTheme } = useEuiTheme(); const statsList = Object.keys(stats).map((k) => stats[k as StatsKey]); return ( -
    +
    {statsList .filter((s: StatsBarStat) => s.show) .map((s: StatsBarStat) => ( diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 339925d3f16ee..d77b66b960625 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -60,7 +60,7 @@ describe('ExplorerChart', () => { // the directive just ends up being empty. expect(wrapper.isEmptyRender()).toBeTruthy(); expect(wrapper.find('.content-wrapper')).toHaveLength(0); - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); }); test('Loading status active, no chart', () => { @@ -84,7 +84,7 @@ describe('ExplorerChart', () => { // test if the loading indicator is shown // Added span because class appears twice with classNames and Emotion - expect(wrapper.find('.ml-loading-indicator span.euiLoadingChart')).toHaveLength(1); + expect(wrapper.find('span.euiLoadingChart')).toHaveLength(1); }); // For the following tests the directive needs to be rendered in the actual DOM, @@ -121,7 +121,7 @@ describe('ExplorerChart', () => { const wrapper = init(mockChartData); // the loading indicator should not be shown - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); // test if all expected elements are present // need to use getDOMNode() because the chart is not rendered via react itself diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 3748a196e742d..abe8c0c991cc4 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -62,7 +62,7 @@ describe('ExplorerChart', () => { // the directive just ends up being empty. expect(wrapper.isEmptyRender()).toBeTruthy(); expect(wrapper.find('.content-wrapper')).toHaveLength(0); - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); }); test('Loading status active, no chart', () => { @@ -87,7 +87,7 @@ describe('ExplorerChart', () => { // test if the loading indicator is shown // Added span because class appears twice with classNames and Emotion - expect(wrapper.find('.ml-loading-indicator span.euiLoadingChart')).toHaveLength(1); + expect(wrapper.find('span.euiLoadingChart')).toHaveLength(1); }); // For the following tests the directive needs to be rendered in the actual DOM, @@ -126,7 +126,7 @@ describe('ExplorerChart', () => { const wrapper = init(mockChartData); // the loading indicator should not be shown - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); // test if all expected elements are present // need to use getDOMNode() because the chart is not rendered via react itself diff --git a/x-pack/plugins/ml/public/application/jobs/_index.scss b/x-pack/plugins/ml/public/application/jobs/_index.scss deleted file mode 100644 index 318ba1d54c082..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'components/custom_url_editor/index'; -@import 'jobs_list/index'; // SASSTODO: Various EUI overwrites throughout this folder -@import 'new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index'; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 6c5d3d473d413..163bad28d8cc2 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -21,7 +21,6 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe data-test-subj="mlJobCustomUrlForm" > = ({ label={ } - className="url-label" error={invalidLabelError} isInvalid={isInvalidLabel} display="rowCompressed" diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss deleted file mode 100644 index 4af4814be8362..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import 'jobs_list'; - -@import 'components/edit_job_flyout/index'; -@import 'components/job_details/index'; // SASSTODO: Dangerous EUI overwrites -@import 'components/job_filter_bar/index'; // SASSTODO: Dangerous EUI overwrites -@import 'components/job_group/index'; -@import 'components/jobs_list_view/index'; -@import 'components/multi_job_actions/index'; // SASSTODO: Dangerous EUI overwrites diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss deleted file mode 100644 index 824f764de3902..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss +++ /dev/null @@ -1,3 +0,0 @@ -.new-job-button-container { - float: right; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss deleted file mode 100644 index 7018a991ce1dc..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss +++ /dev/null @@ -1,8 +0,0 @@ -.edit-custom-url-panel { - .close-editor-button { - position: relative; - float: right; - top: -$euiSizeXS; - right: 0; - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss deleted file mode 100644 index 2fdf5a9671be3..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'edit_job_flyout'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index 9a7508c634c6b..ac6ad4ae70b8b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -268,7 +268,6 @@ class CustomUrlsUI extends Component { defaultMessage: 'Close custom URL editor', } )} - className="close-editor-button" /> {editor} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss deleted file mode 100644 index 3930bb1cf73a5..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_details'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss deleted file mode 100644 index fe61be550ecac..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss +++ /dev/null @@ -1,87 +0,0 @@ -.tab-contents { - margin: -$euiSizeS; - padding: $euiSizeS; - background-color: $euiColorEmptyShade; - - // SASSTODO: Need to remove bootstrap grid - .col-md-6:nth-child(1) { - // SASSTODO: Why is this 7? - padding-right: 7px; - } - - // SASSTODO: Need to remove bootstrap grid - .col-md-6:nth-child(2) { - // SASSTODO: Why is this 7? - padding-left: 7px; - } - - // SASSTODO: This needs to be rewriten. Tons of EUI overwrites - .job-section { - overflow: auto; - padding: 5px 15px; - background-color: $euiColorLightestShade; - border: 1px solid $euiColorLightShade; - border-radius: $euiBorderRadius; - margin: $euiSizeXS 0; - - .euiTable { - background-color: transparent; - - .euiTableRow:first-child { - .euiTableRowCell { - border-top: 0; - } - } - - .euiTableRow:last-child { - .euiTableRowCell { - border-bottom: 0; - } - } - - .euiTableRow { - .euiTableRowCell { - vertical-align: top; - border-bottom: 1px solid $euiColorLightShade; - - .euiTableCellContent__text { - word-wrap: break-word; - } - } - } - - .euiTableRow:hover { - background-color: inherit; - } - } - - .job-item { - font-size: 12px; - } - - .job-item.header { - font-weight: bold; - } - } - - // SASSTODO: This needs a proper calc - .json-textarea { - height: 500px; - } -} - -.job-messages-table { - max-height: 500px; - overflow: auto; - text-align: left; - - tr:last-child { - td { - border-bottom: none; - } - } - - .euiTableRowCell { - background-color: $euiColorEmptyShade; - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js deleted file mode 100644 index c70c049dd7489..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js +++ /dev/null @@ -1,110 +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 PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiTable, - EuiTableBody, - EuiTableRow, - EuiTableRowCell, - EuiSpacer, -} from '@elastic/eui'; - -function SectionItem({ item }) { - return ( - - {item[0] !== '' && ( - - {item[0]} - - )} - - {item[1]} - - - ); -} -SectionItem.propTypes = { - item: PropTypes.array.isRequired, -}; - -function Section({ section }) { - if (section.items.length === 0) { - return
    ; - } - - return ( - - - - -

    {section.title}

    -
    -
    - {section.titleAction} -
    -
    - - - {section.items.map((item, i) => ( - - ))} - - -
    -
    - ); -} -Section.propTypes = { - section: PropTypes.object.isRequired, -}; - -export class JobDetailsPane extends Component { - constructor(props) { - super(props); - this.state = {}; - } - - static getDerivedStateFromProps(props) { - const { sections, time } = props; - return { sections, time }; - } - - render() { - const { sections, time } = this.state; - return ( - - -
    -
    - {sections - .filter((s) => s.position === 'left') - .map((s, i) => ( -
    - ))} -
    -
    - {sections - .filter((s) => s.position === 'right') - .map((s, i) => ( -
    - ))} -
    -
    -
    - ); - } -} -JobDetailsPane.propTypes = { - sections: PropTypes.array.isRequired, - 'data-test-subj': PropTypes.string, -}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx new file mode 100644 index 0000000000000..9d090425ff4ef --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx @@ -0,0 +1,142 @@ +/* + * 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 { css } from '@emotion/react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiTable, + EuiTableBody, + EuiTableRow, + EuiTableRowCell, + EuiSpacer, + useEuiTheme, +} from '@elastic/eui'; + +type Item = [string, string]; + +interface Section { + id: string; + title: string; + titleAction: string; + position: 'left' | 'right'; + items: Item[]; +} + +// fix for the annotation label being hidden inside the bounds of the chart container + +const SectionItem: FC<{ item: Item }> = ({ item }) => { + const { euiTheme } = useEuiTheme(); + const fontSize = euiTheme.size.m; + + return ( + + {item[0] !== '' && ( + + {item[0]} + + )} + + {item[1]} + + + ); +}; + +const Section: FC<{ section: Section }> = ({ section }) => { + const { euiTheme } = useEuiTheme(); + if (section.items.length === 0) { + return
    ; + } + + const cssOverride = css({ + overflow: 'auto', + padding: `${euiTheme.size.xs} ${euiTheme.size.m}`, + backgroundColor: euiTheme.colors.lightestShade, + border: `1px solid ${euiTheme.colors.lightShade}`, + borderRadius: euiTheme.border.radius.medium, + margin: `${euiTheme.size.s} 0`, + + '.euiTable': { + backgroundColor: 'transparent', + }, + + '.euiTableRow:hover': { + backgroundColor: 'inherit', + }, + '.euiTableRow:first-child': { + '.euiTableRowCell': { + borderTop: 0, + }, + }, + '.euiTableRow:last-child': { + '.euiTableRowCell': { + borderBottom: 0, + }, + }, + }); + + return ( + <> + + + + + +

    {section.title}

    +
    +
    + {section.titleAction} +
    +
    + +
    + + + {section.items.map((item, i) => ( + + ))} + + +
    +
    +
    + + ); +}; + +export const JobDetailsPane: FC<{ sections: any[]; 'data-test-subj': string }> = ({ + sections, + ...props +}) => { + return ( + <> + +
    + + + {sections + .filter((s) => s.position === 'left') + .map((s, i) => ( +
    + ))} + + + {sections + .filter((s) => s.position === 'right') + .map((s, i) => ( +
    + ))} + + +
    + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss deleted file mode 100644 index bd0e4ba6a9d45..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_filter_bar'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss deleted file mode 100644 index ecea309314dce..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss +++ /dev/null @@ -1,23 +0,0 @@ -.mlJobFilterBar { - // SASSTODO: Dangerou EUI overwrites - .euiFilterGroup { - .euiPopover .euiPanel { - .group-item { - padding: $euiSizeS $euiSize; - } - - .inline-group { - border: 1px solid $euiColorEmptyShade; - border-radius: $euiBorderRadius; - } - - .euiFilterSelectItem:hover, .euiFilterSelectItem:focus { - text-decoration: none; - .inline-group { - border: 1px solid $euiColorDarkShade; - box-shadow: 0 1px 2px $euiColorMediumShade; - } - } - } - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx index dbc64d082f117..0384b8895dd6c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx @@ -155,7 +155,6 @@ export const JobFilterBar: FC = ({ queryText, setFilters }) = query={queryInstance} filters={filters} onChange={onChange} - className="mlJobFilterBar" /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss deleted file mode 100644 index 361f78b08fcc9..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_group'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss deleted file mode 100644 index fc5bce54afb6a..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss +++ /dev/null @@ -1,10 +0,0 @@ -.inline-group { - font-size: 12px; - background-color: $euiColorLightShade; - padding: 2px 5px; - border-radius: 2px; - display: inline-block; - margin: 0 3px; - color: $euiColorEmptyShade; - vertical-align: text-top; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss deleted file mode 100644 index c1d8b70a054a2..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'jobs_list_view'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss deleted file mode 100644 index ef0fbc358193e..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss +++ /dev/null @@ -1,10 +0,0 @@ -// SASSTODO: Proper calcs -.actions-bar { - min-height: 60px; - display: flex; - align-items: center; -} - -.job-management { - padding: 20px; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 179d5d48b3fe5..75ada48dbdc4c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -418,23 +418,33 @@ export class JobsListView extends Component {
    -
    - this.refreshJobSummaryList()} - /> - -
    + + + this.refreshJobSummaryList()} + /> + + + + + + {groups.map((g, index) => (
    this.closePopover()} > -
    +
    0; + return (
    - {jobsSelected && ( + {jobsSelected ? ( -
    +
    @@ -74,7 +83,7 @@ export class MultiJobActions extends Component { /> - )} + ) : null}
    ); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts index ed0c4639c47c2..aab22b84f8fc6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts @@ -186,7 +186,7 @@ export async function getVisTypeFactory(lens: LensPublicStart) { export async function isCompatibleVisualizationType(chartInfo: ChartInfo) { return ( chartInfo.visualizationType === COMPATIBLE_VISUALIZATION && - chartInfo.layers.some((l) => l.layerType === layerTypes.DATA) + chartInfo.layers.some((l) => l.layerType === layerTypes.DATA && l.dataView !== undefined) ); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts index 1128bd5918814..c7f5a02e75d5b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts @@ -55,7 +55,9 @@ export class VisualizationExtractor { ); } - const timeField = layer.dimensions.find(({ operation }) => operation.dataType === 'date'); + const timeField = layer.dimensions.find( + (dimension) => dimension.operation?.dataType === 'date' + ); if (timeField === undefined || !timeField.operation.fields?.length) { throw Error( i18n.translate('xpack.ml.newJob.fromLens.createJob.error.noDateField', { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx index cd2156b059f47..f0043ca3ea47b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -9,6 +9,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useMlKibana } from '../../../../../../../../../contexts/kibana'; export const Description: FC = memo(({ children }) => { @@ -22,10 +23,21 @@ export const Description: FC = memo(({ children }) => { defaultMessage: 'Custom URLs', } ); + + const cssOverride = css({ + '> .euiFlexGroup': { + '> .euiFlexItem': { + '&:last-child': { + flexBasis: '50%', + }, + }, + }, + }); + return ( {title}} description={ .euiFlexGroup { - > .euiFlexItem { - &:first-child { - max-width: 388px; - } - &:last-child { - flex-basis: 50%; - } - } - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx index 0f8a7c70fe207..9cdd208fbfccd 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx index 6b207928b4c99..daa9ac0ddbc53 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx index 31b9821292758..7cfabf25bad88 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx index 3c329d1c2f092..18c4e419243a9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; interface Props { children: React.ReactNode; @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx index 25b73ed2f5e1c..287a909911605 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.geoField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx index d27953d2e8e10..48e62fc62051b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.populationField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx index 78f0f62294da5..15b618386538a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.splitRareField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx index cf75b9c60852e..8471aba98c636 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.sparseData.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - {children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index 4dc7b498144cf..309b751a0da5d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -135,6 +135,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) disabled={false} callback={fullTimeRangeCallback} timefilter={timefilter} + apiPath="/api/ml/fields_service/time_field_range" /> diff --git a/x-pack/plugins/ml/public/application/overview/_index.scss b/x-pack/plugins/ml/public/application/overview/_index.scss deleted file mode 100644 index 841415620d691..0000000000000 --- a/x-pack/plugins/ml/public/application/overview/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'components/index'; diff --git a/x-pack/plugins/ml/public/application/overview/components/_index.scss b/x-pack/plugins/ml/public/application/overview/components/_index.scss deleted file mode 100644 index 10abd3d6a25d5..0000000000000 --- a/x-pack/plugins/ml/public/application/overview/components/_index.scss +++ /dev/null @@ -1,8 +0,0 @@ -.mlOverviewPanel__isLoading { - text-align: center; - padding: 10%; -} - -.mlOverviewPanel__spinner { - display: inline-block; -} diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx index e5c1bb867bbd9..094693be8787e 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx @@ -75,8 +75,6 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { ); - const panelClass = isInitialized === false ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel'; - const noDFAJobs = errorMessage === undefined && isInitialized === true && analytics.length === 0; return ( @@ -84,10 +82,14 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { {noDFAJobs ? ( ) : ( - + {typeof errorMessage !== 'undefined' ? errorDisplay : null} {isInitialized === false && ( - + )} {isInitialized === true && analytics.length > 0 && ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index 4300d73ae010e..a60fbae514e7f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -127,14 +127,13 @@ export const multiMetricRouteFactory = ( // redirect route to reset the job wizard when converting to multi metric job export const multiMetricRouteFactoryRedirect = (): MlRoute => ({ path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC), - render: (props) => ( - - ), + render: (props) => { + return ( + + ); + }, breadcrumbs: [], }); diff --git a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx index c9163b71b057c..1bbf8c18cf25c 100644 --- a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx @@ -70,7 +70,6 @@ export function createFlyout( { 'data-test-subj': 'mlFlyoutLayerSelector', ownFocus: true, - closeButtonAriaLabel: 'jobSelectorFlyout', onClose: onFlyoutClose, // @ts-expect-error should take any number/string compatible with the CSS width attribute size: '35vw', diff --git a/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts b/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts index 9cb4da16765a3..877c13e93c373 100644 --- a/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts +++ b/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts @@ -42,7 +42,6 @@ journey('Exploratory view', async ({ page, params }) => { await loginToKibana({ page, user: { username: 'elastic', password: 'changeme' }, - dismissTour: false, }); }); diff --git a/x-pack/plugins/observability/e2e/journeys/single_metric.journey.ts b/x-pack/plugins/observability/e2e/journeys/single_metric.journey.ts index 37802127f402f..6fcdb71ccffa2 100644 --- a/x-pack/plugins/observability/e2e/journeys/single_metric.journey.ts +++ b/x-pack/plugins/observability/e2e/journeys/single_metric.journey.ts @@ -45,7 +45,6 @@ journey('SingleMetric', async ({ page, params }) => { await loginToKibana({ page, user: { username: 'elastic', password: 'changeme' }, - dismissTour: false, }); }); diff --git a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts index 13cde8e940e13..5bd6d4c1222b1 100644 --- a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts +++ b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts @@ -51,7 +51,6 @@ journey('Exploratory view', async ({ page, params }) => { await loginToKibana({ page, user: { username: 'elastic', password: 'changeme' }, - dismissTour: false, }); }); diff --git a/x-pack/plugins/observability/e2e/utils.ts b/x-pack/plugins/observability/e2e/utils.ts index 0b570dc5cc5ac..f340ef8b78b3a 100644 --- a/x-pack/plugins/observability/e2e/utils.ts +++ b/x-pack/plugins/observability/e2e/utils.ts @@ -17,11 +17,9 @@ export async function waitForLoadingToFinish({ page }: { page: Page }) { export async function loginToKibana({ page, user, - dismissTour = true, }: { page: Page; user?: { username: string; password: string }; - dismissTour?: boolean; }) { await page.fill('[data-test-subj=loginUsername]', user?.username ?? 'elastic', { timeout: 60 * 1000, @@ -32,10 +30,6 @@ export async function loginToKibana({ await page.click('[data-test-subj=loginSubmit]'); await waitForLoadingToFinish({ page }); - if (dismissTour) { - // Close Monitor Management tour added in 8.2.0 - await page.click('[data-test-subj=syntheticsManagementTourDismiss]'); - } } export const byTestId = (testId: string) => { diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index ad5fac050d671..4026d9f41088d 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -17,6 +17,7 @@ "data", "dataViews", "features", + "files", "inspector", "ruleRegistry", "triggersActionsUi", @@ -25,7 +26,6 @@ "security", "guidedOnboarding", "share", - "spaces" ], "optionalPlugins": [ "discover", diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx index 6647d2499159c..48747afe53305 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx @@ -91,6 +91,7 @@ export function getExploratoryViewEmbeddable( ); const { dataViews, loading } = useAppDataView({ + series, dataViewCache, dataViewsService, dataTypesIndexPatterns, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts index 768a0ac46238c..99df5a1fb6952 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts @@ -12,13 +12,16 @@ import type { ExploratoryEmbeddableProps, ObservabilityPublicPluginsStart } from import type { DataViewState } from '../hooks/use_app_data_view'; import type { AppDataType } from '../types'; import { ObservabilityDataViews } from '../../../../utils/observability_data_views/observability_data_views'; +import { SeriesUrl } from '../../../..'; export const useAppDataView = ({ + series, dataViewCache, seriesDataType, dataViewsService, dataTypesIndexPatterns, }: { + series: SeriesUrl; seriesDataType: AppDataType; dataViewCache: Record; dataViewsService: ObservabilityPublicPluginsStart['dataViews']; @@ -56,10 +59,11 @@ export const useAppDataView = ({ ); useEffect(() => { - if (seriesDataType) { + if (seriesDataType && !loading && !dataViews[seriesDataType]) { loadIndexPattern({ dataType: seriesDataType }); } - }, [seriesDataType, loadIndexPattern]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [seriesDataType, loadIndexPattern, JSON.stringify(series)]); return { dataViews, loading }; }; diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index 373b57e0e61af..eec23d6dcab78 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -95,7 +95,8 @@ export function FieldValueSelection({ useEffect(() => { setOptions(formatOptions(values, selectedValue, excludedValue, showCount)); - }, [values, selectedValue, showCount, excludedValue]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(values), JSON.stringify(selectedValue), showCount, excludedValue]); const onButtonClick = () => { setIsPopoverOpen(!isPopoverOpen); diff --git a/x-pack/plugins/observability/public/components/slo/slo_status_badge/index.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/index.tsx new file mode 100644 index 0000000000000..c77b46d62c2a0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/index.tsx @@ -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 { SloStatusBadge } from './slo_status_badge'; diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.stories.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.stories.tsx similarity index 78% rename from x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.stories.tsx rename to x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.stories.tsx index 5ab384419dd28..1c1313beb15cf 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.stories.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.stories.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; -import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { SloStatusBadge as Component, SloStatusProps } from './slo_status_badge'; -import { buildSlo } from '../../../../data/slo/slo'; +import { buildSlo } from '../../../data/slo/slo'; export default { component: Component, - title: 'app/SLO/ListPage/Badges/SloStatusBadge', + title: 'app/SLO/Badges/SloStatusBadge', decorators: [KibanaReactStorybookDecorator], }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.tsx similarity index 80% rename from x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx rename to x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.tsx index ed69ebae221e5..ef6e931f76ff4 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_status_badge.tsx @@ -21,7 +21,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) {
    {slo.summary.status === 'NO_DATA' && ( - {i18n.translate('xpack.observability.slos.slo.state.noData', { + {i18n.translate('xpack.observability.slo.sloStatusBadge.noData', { defaultMessage: 'No data', })} @@ -29,7 +29,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { {slo.summary.status === 'HEALTHY' && ( - {i18n.translate('xpack.observability.slos.slo.state.healthy', { + {i18n.translate('xpack.observability.slo.sloStatusBadge.healthy', { defaultMessage: 'Healthy', })} @@ -37,7 +37,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { {slo.summary.status === 'DEGRADING' && ( - {i18n.translate('xpack.observability.slos.slo.state.degrading', { + {i18n.translate('xpack.observability.slo.sloStatusBadge.degrading', { defaultMessage: 'Degrading', })} @@ -45,7 +45,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { {slo.summary.status === 'VIOLATED' && ( - {i18n.translate('xpack.observability.slos.slo.state.violated', { + {i18n.translate('xpack.observability.slo.sloStatusBadge.violated', { defaultMessage: 'Violated', })} @@ -56,7 +56,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) {
    - {i18n.translate('xpack.observability.slos.slo.state.forecasted', { + {i18n.translate('xpack.observability.slo.sloStatusBadge.forecasted', { defaultMessage: 'Forecasted', })} diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts index fe10d84c4f428..7928dbe51b46d 100644 --- a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts @@ -20,6 +20,8 @@ export const useFetchHistoricalSummary = ({ return { isLoading: false, + isInitialLoading: false, + isRefetching: false, isSuccess: false, isError: false, sloHistoricalSummaryResponse: data, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts index 2037c97df52c1..469d98e63538c 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts @@ -10,10 +10,10 @@ import { FetchHistoricalSummaryResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; -const EMPTY_RESPONSE: FetchHistoricalSummaryResponse = {}; - export interface UseFetchHistoricalSummaryResponse { - sloHistoricalSummaryResponse: FetchHistoricalSummaryResponse; + sloHistoricalSummaryResponse: FetchHistoricalSummaryResponse | undefined; + isInitialLoading: boolean; + isRefetching: boolean; isLoading: boolean; isSuccess: boolean; isError: boolean; @@ -49,8 +49,10 @@ export function useFetchHistoricalSummary({ }); return { - sloHistoricalSummaryResponse: isInitialLoading ? EMPTY_RESPONSE : data ?? EMPTY_RESPONSE, - isLoading: isInitialLoading || isLoading || isRefetching, + sloHistoricalSummaryResponse: data, + isLoading, + isRefetching, + isInitialLoading, isSuccess, isError, }; diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 648efb5481d31..82e675bcc8437 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -5,11 +5,13 @@ * 2.0. */ +import { useState } from 'react'; import { QueryObserverResult, RefetchOptions, RefetchQueryFilters, useQuery, + useQueryClient, } from '@tanstack/react-query'; import { FindSLOResponse } from '@kbn/slo-schema'; @@ -20,6 +22,7 @@ interface SLOListParams { page?: number; sortBy?: string; indicatorTypes?: string[]; + shouldRefetch?: boolean; } export interface UseFetchSloListResponse { @@ -33,13 +36,20 @@ export interface UseFetchSloListResponse { ) => Promise>; } +const SHORT_REFETCH_INTERVAL = 1000 * 5; // 5 seconds +const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute + export function useFetchSloList({ name = '', page = 1, sortBy = 'name', indicatorTypes = [], + shouldRefetch, }: SLOListParams | undefined = {}): UseFetchSloListResponse { const { http } = useKibana().services; + const queryClient = useQueryClient(); + + const [stateRefetchInterval, setStateRefetchInterval] = useState(SHORT_REFETCH_INTERVAL); const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( { @@ -64,9 +74,29 @@ export function useFetchSloList({ // ignore error } }, - refetchOnWindowFocus: false, keepPreviousData: true, + refetchOnWindowFocus: false, + refetchInterval: shouldRefetch ? stateRefetchInterval : undefined, staleTime: 1000, + onSuccess: ({ results }: FindSLOResponse) => { + if (!shouldRefetch) { + return; + } + + if (results.find((slo) => slo.summary.status === 'NO_DATA')) { + setStateRefetchInterval(SHORT_REFETCH_INTERVAL); + } else { + setStateRefetchInterval(LONG_REFETCH_INTERVAL); + } + + queryClient.invalidateQueries(['fetchHistoricalSummary'], { + exact: false, + }); + + queryClient.invalidateQueries(['fetchActiveAlerts'], { + exact: false, + }); + }, } ); diff --git a/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx b/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx index 11716e948d855..8f2ebf187dc77 100644 --- a/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx +++ b/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx @@ -15,7 +15,7 @@ export const useKibanaSpace = () => { data: space, loading, error, - } = useFetcher>(() => { + } = useFetcher | undefined>(() => { return services.spaces?.getActiveSpace(); }, [services.spaces]); diff --git a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts index 78fffde25d629..dd9581bd67867 100644 --- a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts +++ b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts @@ -29,9 +29,22 @@ export const useOverviewMetrics = ({ hasAnyData }: { hasAnyData: boolean | undef } CAPABILITIES_KEYS.forEach((feature) => { - if (capabilities[feature].show === false) { + const name = feature === 'infrastructure' ? 'metrics' : feature; + + // Track metric if the feature has been disabled, either because it + // is missing or has show === false (manual disabling may not be + // possible in all versions of Kibana) + if (!capabilities[feature] || capabilities[feature]?.show === false) { + trackMetric({ + metric: `oblt_disabled_feature_${name}`, + }); + } + + // Track a separate metric if the feature is missing from the capabilities + // (This usually means the plugin was auto-disabled by Kibana) + if (!capabilities[feature]) { trackMetric({ - metric: `oblt_disabled_feature_${feature === 'infrastructure' ? 'metrics' : feature}`, + metric: `oblt_missing_feature_${name}`, }); } }); diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx new file mode 100644 index 0000000000000..ed984e5ea4a59 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; + +import { ChartData } from '../../../typings/slo'; +import { toHighPrecisionPercentage } from '../helpers/number'; +import { WideChart } from './wide_chart'; + +export interface Props { + data: ChartData[]; + isLoading: boolean; + slo: SLOWithSummaryResponse; +} + +export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) { + const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED'; + + return ( + + + + + +

    + {i18n.translate('xpack.observability.slo.sloDetails.errorBudgetChartPanel.title', { + defaultMessage: 'Error budget burn down', + })} +

    +
    +
    + + + {i18n.translate('xpack.observability.slo.sloDetails.errorBudgetChartPanel.duration', { + defaultMessage: 'Last {duration}', + values: { duration: slo.timeWindow.duration }, + })} + + +
    + + + + + + + + + + +
    +
    + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.stories.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.stories.tsx new file mode 100644 index 0000000000000..af6338d4a3977 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.stories.tsx @@ -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 React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; +import { buildSlo } from '../../../data/slo/slo'; +import { HeaderControl as Component, Props } from './header_control'; + +export default { + component: Component, + title: 'app/SLO/DetailsPage/HeaderControl', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = (props: Props) => ; + +const defaultProps: Props = { + slo: buildSlo(), + isLoading: false, +}; + +export const Default = Template.bind({}); +Default.args = defaultProps; + +export const WithLoading = Template.bind({}); +WithLoading.args = { slo: undefined, isLoading: true }; diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx new file mode 100644 index 0000000000000..fdd0beb82fce9 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -0,0 +1,78 @@ +/* + * 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 { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; + +import { paths } from '../../../config'; +import { useKibana } from '../../../utils/kibana_react'; +import { ObservabilityAppServices } from '../../../application/types'; +import { useCapabilities } from '../../../hooks/slo/use_capabilities'; + +export interface Props { + slo: SLOWithSummaryResponse | undefined; + isLoading: boolean; +} + +export function HeaderControl({ isLoading, slo }: Props) { + const { + application: { navigateToUrl }, + http: { basePath }, + } = useKibana().services; + const { hasWriteCapabilities } = useCapabilities(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const handleActionsClick = () => setIsPopoverOpen((value) => !value); + const closePopover = () => setIsPopoverOpen(false); + + const handleEdit = () => { + if (slo) { + navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); + } + }; + + return ( + + {i18n.translate('xpack.observability.slo.sloDetails.headerControl.actions', { + defaultMessage: 'Actions', + })} + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + > + + {i18n.translate('xpack.observability.slo.sloDetails.headerControl.edit', { + defaultMessage: 'Edit', + })} + , + ]} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_title.stories.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.stories.tsx new file mode 100644 index 0000000000000..daf5e8038fdd3 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.stories.tsx @@ -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 React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; +import { buildSlo } from '../../../data/slo/slo'; +import { HeaderTitle as Component, Props } from './header_title'; + +export default { + component: Component, + title: 'app/SLO/DetailsPage/HeaderTitle', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = (props: Props) => ; + +const defaultProps: Props = { + slo: buildSlo(), + isLoading: false, +}; + +export const Default = Template.bind({}); +Default.args = defaultProps; + +export const WithLoading = Template.bind({}); +WithLoading.args = { slo: undefined, isLoading: false }; diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/page_title.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx similarity index 55% rename from x-pack/plugins/observability/public/pages/slo_details/components/page_title.tsx rename to x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx index a4803a83eeb17..d2448d8959ad7 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/page_title.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx @@ -5,20 +5,31 @@ * 2.0. */ -import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { SloStatusBadge } from '../../../components/slo/slo_status_badge'; + export interface Props { slo: SLOWithSummaryResponse | undefined; isLoading: boolean; } -export function PageTitle(props: Props) { +export function HeaderTitle(props: Props) { const { isLoading, slo } = props; if (isLoading) { return ; } - return <>{slo && slo.name}; + return ( + + {slo && slo.name} + {!!slo && ( + + + + )} + + ); } diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/overview.stories.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/overview.stories.tsx new file mode 100644 index 0000000000000..3a3666d901304 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/overview.stories.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; +import { buildSlo } from '../../../data/slo/slo'; +import { Overview as Component, Props } from './overview'; + +export default { + component: Component, + title: 'app/SLO/DetailsPage/Overview', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = (props: Props) => ; + +const defaultProps: Props = { + slo: buildSlo(), +}; + +export const Overview = Template.bind({}); +Overview.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/overview.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/overview.tsx new file mode 100644 index 0000000000000..24de662f5ce05 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/overview.tsx @@ -0,0 +1,157 @@ +/* + * 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 { EuiFlexGroup, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { assertNever } from '@kbn/std'; +import moment from 'moment'; +import React from 'react'; +import { DEFAULT_DATE_FORMAT } from '../constants'; +import { toHighPrecisionPercentage } from '../helpers/number'; + +import { OverviewItem } from './overview_item'; + +export interface Props { + slo: SLOWithSummaryResponse; +} + +export function Overview({ slo }: Props) { + const hasNoData = slo.summary.status === 'NO_DATA'; + return ( + + + + + + + + + + + + + + + + + + ); +} + +function toTimeWindowLabel(timeWindow: SLOWithSummaryResponse['timeWindow']): string { + if ('isRolling' in timeWindow) { + return i18n.translate('xpack.observability.slo.sloDetails.overview.rollingTimeWindow', { + defaultMessage: '{duration} rolling', + values: { + duration: timeWindow.duration, + }, + }); + } + + return i18n.translate('xpack.observability.slo.sloDetails.overview.calendarAlignedTimeWindow', { + defaultMessage: '{duration}', + values: { + duration: timeWindow.duration, + }, + }); +} + +function toIndicatorTypeLabel(indicatorType: SLOWithSummaryResponse['indicator']['type']): string { + switch (indicatorType) { + case 'sli.kql.custom': + return i18n.translate('xpack.observability.slo.sloDetails.overview.customKqlIndicator', { + defaultMessage: 'Custom KQL', + }); + + case 'sli.apm.transactionDuration': + return i18n.translate('xpack.observability.slo.sloDetails.overview.apmLatencyIndicator', { + defaultMessage: 'APM latency', + }); + + case 'sli.apm.transactionErrorRate': + return i18n.translate( + 'xpack.observability.slo.sloDetails.overview.apmAvailabilityIndicator', + { + defaultMessage: 'APM availability', + } + ); + default: + assertNever(indicatorType); + } +} + +function toBudgetingMethod(budgetingMethod: SLOWithSummaryResponse['budgetingMethod']): string { + if (budgetingMethod === 'occurrences') { + return i18n.translate( + 'xpack.observability.slo.sloDetails.overview.occurrencesBudgetingMethod', + { defaultMessage: 'Occurrences' } + ); + } + + return i18n.translate('xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethod', { + defaultMessage: 'Timeslices', + }); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/overview_item.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/overview_item.tsx new file mode 100644 index 0000000000000..ccc4a61a5d7f0 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/overview_item.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import React from 'react'; + +export interface Props { + title: string; + subtitle: string; +} + +export function OverviewItem({ title, subtitle }: Props) { + return ( + + + + + {title} + + + + {subtitle} + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/sli_chart_panel.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/sli_chart_panel.tsx new file mode 100644 index 0000000000000..a40372b583b8b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/sli_chart_panel.tsx @@ -0,0 +1,93 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; + +import { ChartData } from '../../../typings/slo'; +import { toHighPrecisionPercentage } from '../helpers/number'; +import { WideChart } from './wide_chart'; + +export interface Props { + data: ChartData[]; + isLoading: boolean; + slo: SLOWithSummaryResponse; +} + +export function SliChartPanel({ data, isLoading, slo }: Props) { + const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED'; + const hasNoData = slo.summary.status === 'NO_DATA'; + + return ( + + + + + +

    + {i18n.translate('xpack.observability.slo.sloDetails.sliHistoryChartPanel.title', { + defaultMessage: 'Historical SLI', + })} +

    +
    +
    + + + {i18n.translate('xpack.observability.slo.sloDetails.sliHistoryChartPanel.duration', { + defaultMessage: 'Last {duration}', + values: { duration: slo.timeWindow.duration }, + })} + + +
    + + + + + + + + + + + + + +
    +
    + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx index bf462b7c360bb..237af2c0698ae 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx @@ -8,13 +8,14 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { buildSlo } from '../../../data/slo/slo'; import { SloDetails as Component, Props } from './slo_details'; export default { component: Component, title: 'app/SLO/DetailsPage/SloDetails', - argTypes: {}, + decorators: [KibanaReactStorybookDecorator], }; const Template: ComponentStory = (props: Props) => ; 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 ed717865a710d..4ba0f333f892c 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,14 +5,47 @@ * 2.0. */ +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from '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'; +import { Overview as Overview } from './overview'; +import { SliChartPanel } from './sli_chart_panel'; + export interface Props { slo: SLOWithSummaryResponse; } -export function SloDetails(props: Props) { - const { slo } = props; - return
    {JSON.stringify(slo, null, 2)}
    ; +export function SloDetails({ slo }: Props) { + const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse = {} } = + useFetchHistoricalSummary({ sloIds: [slo.id] }); + + const errorBudgetBurnDownData = formatHistoricalData( + sloHistoricalSummaryResponse[slo.id], + 'error_budget_remaining' + ); + const historicalSliData = formatHistoricalData(sloHistoricalSummaryResponse[slo.id], 'sli_value'); + + return ( + + + + + + + + + + + + + + ); } diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/wide_chart.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/wide_chart.tsx new file mode 100644 index 0000000000000..ae9f5701f3cf6 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/wide_chart.tsx @@ -0,0 +1,91 @@ +/* + * 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 { + AreaSeries, + Axis, + Chart, + Fit, + LineSeries, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import React from 'react'; +import { EuiIcon, EuiLoadingChart, useEuiTheme } from '@elastic/eui'; + +import moment from 'moment'; +import { ChartData } from '../../../typings'; +import { useKibana } from '../../../utils/kibana_react'; +import { toHighPrecisionPercentage } from '../helpers/number'; +import { DEFAULT_DATE_FORMAT } from '../constants'; + +type ChartType = 'area' | 'line'; +type State = 'success' | 'error'; + +export interface Props { + id: string; + data: ChartData[]; + chart: ChartType; + state: State; + isLoading: boolean; +} + +export function WideChart({ chart, data, id, isLoading, state }: Props) { + const charts = useKibana().services.charts; + const theme = charts.theme.useChartsTheme(); + const baseTheme = charts.theme.useChartsBaseTheme(); + const { euiTheme } = useEuiTheme(); + + const color = state === 'error' ? euiTheme.colors.danger : euiTheme.colors.success; + const ChartComponent = chart === 'area' ? AreaSeries : LineSeries; + + if (isLoading) { + return ; + } + + return ( + + } + /> + moment(d).format(DEFAULT_DATE_FORMAT)} + /> + `${toHighPrecisionPercentage(d)}%`} + /> + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/constants.ts b/x-pack/plugins/observability/public/pages/slo_details/constants.ts new file mode 100644 index 0000000000000..b70a27b81eade --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/constants.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 const DEFAULT_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; diff --git a/x-pack/plugins/observability/public/pages/slo_details/helpers/number.ts b/x-pack/plugins/observability/public/pages/slo_details/helpers/number.ts new file mode 100644 index 0000000000000..6d8c67d4d12fc --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/helpers/number.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 function toHighPrecisionPercentage(value: number): number { + return Math.trunc(value * 100000) / 1000; +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx index bb5d481df9282..291ddeb9eaf48 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx @@ -18,6 +18,10 @@ import { buildSlo } from '../../data/slo/slo'; import type { ConfigSchema } from '../../plugin'; import type { Subset } from '../../typings'; import { paths } from '../../config'; +import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary'; +import { useCapabilities } from '../../hooks/slo/use_capabilities'; +import { historicalSummaryData } from '../../data/slo/historical_summary_data'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -28,11 +32,15 @@ jest.mock('../../utils/kibana_react'); jest.mock('../../hooks/use_breadcrumbs'); jest.mock('../../hooks/use_license'); jest.mock('../../hooks/slo/use_fetch_slo_details'); +jest.mock('../../hooks/slo/use_fetch_historical_summary'); +jest.mock('../../hooks/slo/use_capabilities'); const useKibanaMock = useKibana as jest.Mock; const useParamsMock = useParams as jest.Mock; const useLicenseMock = useLicense as jest.Mock; const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock; +const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; +const useCapabilitiesMock = useCapabilities as jest.Mock; const mockNavigate = jest.fn(); const mockBasePathPrepend = jest.fn(); @@ -41,6 +49,7 @@ const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { application: { navigateToUrl: mockNavigate }, + charts: chartPluginMock.createSetupContract(), http: { basePath: { prepend: mockBasePathPrepend, @@ -60,6 +69,11 @@ describe('SLO Details Page', () => { beforeEach(() => { jest.clearAllMocks(); mockKibana(); + useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true }); + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: historicalSummaryData, + }); }); describe('when the feature flag is not enabled', () => { @@ -110,7 +124,7 @@ describe('SLO Details Page', () => { expect(screen.queryByTestId('pageNotFound')).toBeFalsy(); expect(screen.queryByTestId('loadingTitle')).toBeTruthy(); - expect(screen.queryByTestId('loadingDetails')).toBeTruthy(); + expect(screen.queryByTestId('sloDetailsLoading')).toBeTruthy(); }); it('renders the SLO details page', async () => { @@ -122,7 +136,6 @@ describe('SLO Details Page', () => { render(, config); expect(screen.queryByTestId('sloDetailsPage')).toBeTruthy(); - expect(screen.queryByTestId('sloDetails')).toBeTruthy(); }); }); }); diff --git a/x-pack/plugins/observability/public/pages/slo_details/index.tsx b/x-pack/plugins/observability/public/pages/slo_details/index.tsx index 2ba02c9464c55..dc0770b0f4ef0 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/index.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/index.tsx @@ -11,7 +11,8 @@ import { EuiBreadcrumbProps } from '@elastic/eui/src/components/breadcrumbs/brea import { EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { IBasePath } from '@kbn/core-http-browser'; -import type { SLOResponse } from '@kbn/slo-schema'; +import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; + import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; @@ -20,29 +21,26 @@ import { useLicense } from '../../hooks/use_license'; import PageNotFound from '../404'; import { isSloFeatureEnabled } from '../slos/helpers/is_slo_feature_enabled'; import { SloDetails } from './components/slo_details'; -import { PageTitle } from './components/page_title'; +import { HeaderTitle } from './components/header_title'; import { paths } from '../../config'; import type { SloDetailsPathParams } from './types'; import type { ObservabilityAppServices } from '../../application/types'; +import { HeaderControl } from './components/header_control'; export function SloDetailsPage() { const { application: { navigateToUrl }, http: { basePath }, } = useKibana().services; - const { ObservabilityPageTemplate, config } = usePluginContext(); - const { sloId } = useParams(); - const { hasAtLeast } = useLicense(); const hasRightLicense = hasAtLeast('platinum'); + const { sloId } = useParams(); const { isLoading, slo } = useFetchSloDetails(sloId); - useBreadcrumbs(getBreadcrumbs(basePath, slo)); const isSloNotFound = !isLoading && slo === undefined; - if (!isSloFeatureEnabled(config) || isSloNotFound) { return ; } @@ -54,19 +52,22 @@ export function SloDetailsPage() { return ( , - rightSideItems: [], - bottomBorder: true, + pageTitle: , + rightSideItems: [], + bottomBorder: false, }} data-test-subj="sloDetailsPage" > - {isLoading && } + {isLoading && } {!isLoading && } ); } -function getBreadcrumbs(basePath: IBasePath, slo: SLOResponse | undefined): EuiBreadcrumbProps[] { +function getBreadcrumbs( + basePath: IBasePath, + slo: SLOWithSummaryResponse | undefined +): EuiBreadcrumbProps[] { return [ { href: basePath.prepend(paths.observability.slos), diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx index e61e1c7056596..51cefb5ce1400 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx @@ -33,11 +33,11 @@ export function ApmAvailabilityIndicatorTypeForm() { - {i18n.translate('xpack.observability.slos.sloEdit.apmAvailability.goodStatusCodes', { + {i18n.translate('xpack.observability.slo.sloEdit.apmAvailability.goodStatusCodes', { defaultMessage: 'Good status codes', })} @@ -119,13 +113,13 @@ export function ApmAvailabilityIndicatorTypeForm() { - {i18n.translate('xpack.observability.slos.sloEdit.apmLatency.threshold.placeholder', { + {i18n.translate('xpack.observability.slo.sloEdit.apmLatency.threshold.placeholder', { defaultMessage: 'Threshold (ms)', })} @@ -119,12 +119,12 @@ export function ApmLatencyIndicatorTypeForm() { control={control} dataTestSubj="apmLatencyFilterInput" indexPatternString={watch('indicator.params.index')} - label={i18n.translate('xpack.observability.slos.sloEdit.apmLatency.filter', { + label={i18n.translate('xpack.observability.slo.sloEdit.apmLatency.filter', { defaultMessage: 'Query filter', })} name="indicator.params.filter" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.apmLatency.filter.placeholder', + 'xpack.observability.slo.sloEdit.apmLatency.filter.placeholder', { defaultMessage: 'Custom filter to apply on the index', } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx index a9fb7cd58e480..bdcbb516d8f29 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx @@ -27,12 +27,12 @@ export function CustomKqlIndicatorTypeForm() { control={control} dataTestSubj="customKqlIndicatorFormQueryFilterInput" indexPatternString={watch('indicator.params.index')} - label={i18n.translate('xpack.observability.slos.sloEdit.sliType.customKql.queryFilter', { + label={i18n.translate('xpack.observability.slo.sloEdit.sliType.customKql.queryFilter', { defaultMessage: 'Query filter', })} name="indicator.params.filter" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sliType.customKql.customFilter', + 'xpack.observability.slo.sloEdit.sliType.customKql.customFilter', { defaultMessage: 'Custom filter to apply on the index', } @@ -45,12 +45,12 @@ export function CustomKqlIndicatorTypeForm() { control={control} dataTestSubj="customKqlIndicatorFormGoodQueryInput" indexPatternString={watch('indicator.params.index')} - label={i18n.translate('xpack.observability.slos.sloEdit.sliType.customKql.goodQuery', { + label={i18n.translate('xpack.observability.slo.sloEdit.sliType.customKql.goodQuery', { defaultMessage: 'Good query', })} name="indicator.params.good" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sliType.customKql.goodQueryPlaceholder', + 'xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder', { defaultMessage: 'Define the good events', } @@ -63,12 +63,12 @@ export function CustomKqlIndicatorTypeForm() { control={control} dataTestSubj="customKqlIndicatorFormTotalQueryInput" indexPatternString={watch('indicator.params.index')} - label={i18n.translate('xpack.observability.slos.sloEdit.sliType.customKql.totalQuery', { + label={i18n.translate('xpack.observability.slo.sloEdit.sliType.customKql.totalQuery', { defaultMessage: 'Total query', })} name="indicator.params.total" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sliType.customKql.totalQueryPlaceholder', + 'xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder', { defaultMessage: 'Define the total events', } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/index_selection.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/index_selection.tsx index 07c7b878410f9..1e815f1f2c27c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/index_selection.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/index_selection.tsx @@ -49,7 +49,7 @@ export function IndexSelection({ control }: Props) { const searchWithStarSuffix = search.endsWith('*') ? search : `${search}*`; options.push({ label: i18n.translate( - 'xpack.observability.slos.sloEdit.customKql.indexSelection.indexPatternLabel', + 'xpack.observability.slo.sloEdit.customKql.indexSelection.indexPatternLabel', { defaultMessage: 'Use an index pattern' } ), options: [{ value: searchWithStarSuffix, label: searchWithStarSuffix }], @@ -60,11 +60,11 @@ export function IndexSelection({ control }: Props) { return (

    - {i18n.translate('xpack.observability.slos.sloEdit.definition.title', { + {i18n.translate('xpack.observability.slo.sloEdit.definition.title', { defaultMessage: 'Define SLI', })}

    @@ -162,7 +162,7 @@ export function SloEditForm({ slo }: Props) { - {i18n.translate('xpack.observability.slos.sloEdit.definition.sliType', { + {i18n.translate('xpack.observability.slo.sloEdit.definition.sliType', { defaultMessage: 'SLI type', })} @@ -205,7 +205,7 @@ export function SloEditForm({ slo }: Props) {

    - {i18n.translate('xpack.observability.slos.sloEdit.objectives.title', { + {i18n.translate('xpack.observability.slo.sloEdit.objectives.title', { defaultMessage: 'Set objectives', })}

    @@ -236,7 +236,7 @@ export function SloEditForm({ slo }: Props) {

    - {i18n.translate('xpack.observability.slos.sloEdit.description.title', { + {i18n.translate('xpack.observability.slo.sloEdit.description.title', { defaultMessage: 'Describe SLO', })}

    @@ -258,10 +258,10 @@ export function SloEditForm({ slo }: Props) { onClick={handleSubmit} > {isEditMode - ? i18n.translate('xpack.observability.slos.sloEdit.editSloButton', { + ? i18n.translate('xpack.observability.slo.sloEdit.editSloButton', { defaultMessage: 'Update SLO', }) - : i18n.translate('xpack.observability.slos.sloEdit.createSloButton', { + : i18n.translate('xpack.observability.slo.sloEdit.createSloButton', { defaultMessage: 'Create SLO', })} @@ -272,7 +272,7 @@ export function SloEditForm({ slo }: Props) { fill onClick={() => navigateToUrl(basePath.prepend(paths.observability.slos))} > - {i18n.translate('xpack.observability.slos.sloEdit.cancelButton', { + {i18n.translate('xpack.observability.slo.sloEdit.cancelButton', { defaultMessage: 'Cancel', })} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_description.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_description.tsx index 6b6780358376f..c770335bd1161 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_description.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_description.tsx @@ -27,7 +27,7 @@ export function SloEditFormDescription() { - {i18n.translate('xpack.observability.slos.sloEdit.description.sloName', { + {i18n.translate('xpack.observability.slo.sloEdit.description.sloName', { defaultMessage: 'SLO Name', })} @@ -42,7 +42,7 @@ export function SloEditFormDescription() { id={sloNameId} data-test-subj="sloFormNameInput" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.description.sloNamePlaceholder', + 'xpack.observability.slo.sloEdit.description.sloNamePlaceholder', { defaultMessage: 'Name for the SLO', } @@ -55,7 +55,7 @@ export function SloEditFormDescription() { - {i18n.translate('xpack.observability.slos.sloEdit.description.sloDescription', { + {i18n.translate('xpack.observability.slo.sloEdit.description.sloDescription', { defaultMessage: 'Description', })} @@ -70,7 +70,7 @@ export function SloEditFormDescription() { id={descriptionId} data-test-subj="sloFormDescriptionTextArea" placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.description.sloDescriptionPlaceholder', + 'xpack.observability.slo.sloEdit.description.sloDescriptionPlaceholder', { defaultMessage: 'A short description of the SLO', } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives.tsx index 083224af18595..9f2be82ba9835 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives.tsx @@ -32,7 +32,7 @@ export function SloEditFormObjectives() { - {i18n.translate('xpack.observability.slos.sloEdit.budgetingMethod.label', { + {i18n.translate('xpack.observability.slo.sloEdit.budgetingMethod.label', { defaultMessage: 'Budgeting method', })} @@ -54,7 +54,7 @@ export function SloEditFormObjectives() { - {i18n.translate('xpack.observability.slos.sloEdit.timeWindow.label', { + {i18n.translate('xpack.observability.slo.sloEdit.timeWindow.label', { defaultMessage: 'Time window', })} @@ -77,7 +77,7 @@ export function SloEditFormObjectives() { - {i18n.translate('xpack.observability.slos.sloEdit.targetSlo.label', { + {i18n.translate('xpack.observability.slo.sloEdit.targetSlo.label', { defaultMessage: 'Target / SLO (%)', })} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives_timeslices.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives_timeslices.tsx index 2d18c57c203c4..823a739ca4bcf 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives_timeslices.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objectives_timeslices.tsx @@ -17,7 +17,7 @@ export function SloEditFormObjectivesTimeslices() { - {i18n.translate('xpack.observability.slos.sloEdit.timeSliceTarget.label', { + {i18n.translate('xpack.observability.slo.sloEdit.timeSliceTarget.label', { defaultMessage: 'Timeslice target (%)', })} @@ -47,7 +47,7 @@ export function SloEditFormObjectivesTimeslices() { - {i18n.translate('xpack.observability.slos.sloEdit.timesliceWindow.label', { + {i18n.translate('xpack.observability.slo.sloEdit.timesliceWindow.label', { defaultMessage: 'Timeslice window (minutes)', })} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts index a7e44f34b4f55..d3e42dc76d020 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts @@ -14,19 +14,19 @@ export const SLI_OPTIONS: Array<{ }> = [ { value: 'sli.kql.custom', - text: i18n.translate('xpack.observability.slos.sliTypes.kqlCustomIndicator', { + text: i18n.translate('xpack.observability.slo.sliTypes.kqlCustomIndicator', { defaultMessage: 'KQL custom', }), }, { value: 'sli.apm.transactionDuration', - text: i18n.translate('xpack.observability.slos.sliTypes.apmLatencyIndicator', { + text: i18n.translate('xpack.observability.slo.sliTypes.apmLatencyIndicator', { defaultMessage: 'APM latency', }), }, { value: 'sli.apm.transactionErrorRate', - text: i18n.translate('xpack.observability.slos.sliTypes.apmAvailabilityIndicator', { + text: i18n.translate('xpack.observability.slo.sliTypes.apmAvailabilityIndicator', { defaultMessage: 'APM availability', }), }, @@ -35,13 +35,13 @@ export const SLI_OPTIONS: Array<{ export const BUDGETING_METHOD_OPTIONS: Array<{ value: BudgetingMethod; text: string }> = [ { value: 'occurrences', - text: i18n.translate('xpack.observability.slos.sloEdit.budgetingMethod.occurrences', { + text: i18n.translate('xpack.observability.slo.sloEdit.budgetingMethod.occurrences', { defaultMessage: 'Occurrences', }), }, { value: 'timeslices', - text: i18n.translate('xpack.observability.slos.sloEdit.budgetingMethod.timeslices', { + text: i18n.translate('xpack.observability.slo.sloEdit.budgetingMethod.timeslices', { defaultMessage: 'Timeslices', }), }, @@ -49,7 +49,7 @@ export const BUDGETING_METHOD_OPTIONS: Array<{ value: BudgetingMethod; text: str export const TIMEWINDOW_OPTIONS = [90, 30, 7].map((number) => ({ value: `${number}d`, - text: i18n.translate('xpack.observability.slos.sloEdit.timeWindow.days', { + text: i18n.translate('xpack.observability.slo.sloEdit.timeWindow.days', { defaultMessage: '{number} days', values: { number }, }), diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx new file mode 100644 index 0000000000000..5a5b94401253e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; +import { AutoRefreshButton as Component } from './auto_refresh_button'; + +export default { + component: Component, + title: 'app/SLO/ListPage/AutoRefreshButton', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = () => { + const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + + const toggleEnabled = () => { + setIsAutoRefreshing(!isAutoRefreshing); + }; + + return ; +}; + +const defaultProps = { + enabled: true, + disabled: false, +}; + +export const AutoRefreshButton = Template.bind({}); +AutoRefreshButton.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx new file mode 100644 index 0000000000000..5101a5c1d96ef --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx @@ -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 React from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + isAutoRefreshing: boolean; + dataTestSubj?: string; + disabled?: boolean; + onClick: () => void; +} + +export function AutoRefreshButton({ + dataTestSubj = 'autoRefreshButton', + disabled, + isAutoRefreshing, + onClick, +}: Props) { + return isAutoRefreshing ? ( + + {i18n.translate('xpack.observability.slosPage.stopRefreshingButtonLabel', { + defaultMessage: 'Stop refreshing', + })} + + ) : ( + + {i18n.translate('xpack.observability.slosPage.autoRefreshButtonLabel', { + defaultMessage: 'Auto-refresh', + })} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index cd4c7d9c2d5d5..33baec901097f 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../../utils/kibana_react'; import { paths } from '../../../../config'; import { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; -import { SloStatusBadge } from './slo_status_badge'; +import { SloStatusBadge } from '../../../../components/slo/slo_status_badge'; import { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloTimeWindowBadge } from './slo_time_window_badge'; @@ -52,12 +52,12 @@ export function SloBadges({ slo, activeAlerts }: Props) { color="danger" onClick={handleClick} onClickAriaLabel={i18n.translate( - 'xpack.observability.slos.slo.activeAlertsBadge.ariaLabel', + 'xpack.observability.slo.slo.activeAlertsBadge.ariaLabel', { defaultMessage: 'active alerts badge' } )} data-test-subj="o11ySlosPageSloActiveAlertsBadge" > - {i18n.translate('xpack.observability.slos.slo.activeAlertsBadge.label', { + {i18n.translate('xpack.observability.slo.slo.activeAlertsBadge.label', { defaultMessage: '{count, plural, one {# alert} other {# alerts}}', values: { count: activeAlerts.count }, })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx index 0dde7681b840a..9dcd221cc38bd 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx @@ -28,15 +28,15 @@ export function SloIndicatorTypeBadge({ slo }: Props) { function toIndicatorLabel(indicatorType: SLOWithSummaryResponse['indicator']['type']) { switch (indicatorType) { case 'sli.kql.custom': - return i18n.translate('xpack.observability.slos.slo.indicator.customKql', { + return i18n.translate('xpack.observability.slo.slo.indicator.customKql', { defaultMessage: 'KQL', }); case 'sli.apm.transactionDuration': - return i18n.translate('xpack.observability.slos.slo.indicator.apmLatency', { + return i18n.translate('xpack.observability.slo.slo.indicator.apmLatency', { defaultMessage: 'Latency', }); case 'sli.apm.transactionErrorRate': - return i18n.translate('xpack.observability.slos.slo.indicator.apmAvailability', { + return i18n.translate('xpack.observability.slo.slo.indicator.apmAvailability', { defaultMessage: 'Availability', }); default: diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx index 22f5e6499503e..e4bc2de0eda7b 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx @@ -50,7 +50,7 @@ export function SloTimeWindowBadge({ slo }: Props) { return (
    - {i18n.translate('xpack.observability.slos.slo.timeWindow.calendar', { + {i18n.translate('xpack.observability.slo.slo.timeWindow.calendar', { defaultMessage: '{elapsed}/{total} days', values: { elapsed: Math.min(elapsedDurationInDays, totalDurationInDays), @@ -65,27 +65,27 @@ export function SloTimeWindowBadge({ slo }: Props) { function toDurationLabel(duration: number, durationUnit: string) { switch (durationUnit) { case 'd': - return i18n.translate('xpack.observability.slos.slo.timeWindow.days', { + return i18n.translate('xpack.observability.slo.slo.timeWindow.days', { defaultMessage: '{duration} days', values: { duration }, }); case 'w': - return i18n.translate('xpack.observability.slos.slo.timeWindow.weeks', { + return i18n.translate('xpack.observability.slo.slo.timeWindow.weeks', { defaultMessage: '{duration} weeks', values: { duration }, }); case 'M': - return i18n.translate('xpack.observability.slos.slo.timeWindow.months', { + return i18n.translate('xpack.observability.slo.slo.timeWindow.months', { defaultMessage: '{duration} months', values: { duration }, }); case 'Q': - return i18n.translate('xpack.observability.slos.slo.timeWindow.quarterss', { + return i18n.translate('xpack.observability.slo.slo.timeWindow.quarterss', { defaultMessage: '{duration} quarters', values: { duration }, }); case 'Y': - return i18n.translate('xpack.observability.slos.slo.timeWindow.years', { + return i18n.translate('xpack.observability.slo.slo.timeWindow.years', { defaultMessage: '{duration} years', values: { duration }, }); diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx index 2d753000d7d5d..4a292bef8c14b 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx @@ -52,7 +52,7 @@ export function SloDeleteConfirmationModal({ onCancel={onCancel} onConfirm={handleConfirm} > - {i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.descriptionText', { + {i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.descriptionText', { defaultMessage: "You can't recover {name} after deleting.", values: { name }, })} @@ -61,24 +61,24 @@ export function SloDeleteConfirmationModal({ } const getTitle = () => - i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.title', { + i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.title', { defaultMessage: 'Are you sure?', }); const getCancelButtonText = () => - i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.cancelButtonLabel', { + i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel', { defaultMessage: 'Cancel', }); const getConfirmButtonText = (name: string) => - i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.deleteButtonLabel', { + i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel', { defaultMessage: 'Delete {name}', values: { name }, }); const getDeleteSuccesfulMessage = (name: string) => i18n.translate( - 'xpack.observability.slos.slo.deleteConfirmationModal.successNotification.descriptionText', + 'xpack.observability.slo.slo.deleteConfirmationModal.successNotification.descriptionText', { defaultMessage: 'Deleted {name}', values: { name }, @@ -87,7 +87,7 @@ const getDeleteSuccesfulMessage = (name: string) => const getDeleteFailMessage = (name: string) => i18n.translate( - 'xpack.observability.slos.slo.deleteConfirmationModal.errorNotification.descriptionText', + 'xpack.observability.slo.slo.deleteConfirmationModal.errorNotification.descriptionText', { defaultMessage: 'Failed to delete {name}', values: { name }, diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx index 3e6dd1c87d798..244bda63596b7 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; -import { SloList as Component } from './slo_list'; +import { SloList as Component, Props } from './slo_list'; export default { component: Component, @@ -18,9 +18,11 @@ export default { decorators: [KibanaReactStorybookDecorator], }; -const Template: ComponentStory = () => ; +const Template: ComponentStory = (props: Props) => ; -const defaultProps = {}; +const defaultProps = { + autoRefresh: true, +}; export const SloList = Template.bind({}); SloList.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 9d5c8515d3403..1ca463c1ff940 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -18,7 +18,11 @@ import { } from './slo_list_search_filter_sort_bar'; import { SloListItems } from './slo_list_items'; -export function SloList() { +export interface Props { + autoRefresh: boolean; +} + +export function SloList({ autoRefresh }: Props) { const [activePage, setActivePage] = useState(0); const [query, setQuery] = useState(''); @@ -30,6 +34,7 @@ export function SloList() { name: query, sortBy: sort, indicatorTypes: indicatorTypeFilter, + shouldRefetch: autoRefresh, }); const { results = [], total = 0, perPage = 0 } = sloList || {}; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx index eac54fdafec3d..cac36a8f3bcc9 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx @@ -12,13 +12,13 @@ import { i18n } from '@kbn/i18n'; export function SloListEmpty() { return ( - {i18n.translate('xpack.observability.slos.list.emptyMessage', { + {i18n.translate('xpack.observability.slo.list.emptyMessage', { defaultMessage: 'There are no results for your criteria.', })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx index 47ae04b13c9ab..8e9729ba07621 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx @@ -16,14 +16,14 @@ export function SloListError() { color="danger" title={

    - {i18n.translate('xpack.observability.slos.list.errorTitle', { + {i18n.translate('xpack.observability.slo.list.errorTitle', { defaultMessage: 'Unable to load SLOs', })}

    } body={

    - {i18n.translate('xpack.observability.slos.list.errorMessage', { + {i18n.translate('xpack.observability.slo.list.errorMessage', { defaultMessage: 'There was an error loading the SLOs. Contact your administrator for help.', })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index 282b82bf41b7e..df2b62e0d5848 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -13,6 +13,7 @@ import { EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, + EuiLink, EuiPanel, EuiPopover, EuiText, @@ -62,6 +63,10 @@ export function SloListItem({ setIsActionsPopoverOpen(!isActionsPopoverOpen); }; + const handleViewDetails = () => { + navigateToUrl(basePath.prepend(paths.observability.sloDetails(slo.id))); + }; + const handleEdit = () => { navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); }; @@ -102,7 +107,9 @@ export function SloListItem({ - {slo.name} + + {slo.name} + @@ -139,6 +146,16 @@ export function SloListItem({ + {i18n.translate('xpack.observability.slo.slo.item.actions.details', { + defaultMessage: 'Details', + })} + , - {i18n.translate('xpack.observability.slos.slo.item.actions.edit', { + {i18n.translate('xpack.observability.slo.slo.item.actions.edit', { defaultMessage: 'Edit', })} , @@ -157,7 +174,7 @@ export function SloListItem({ onClick={handleClone} data-test-subj="sloActionsClone" > - {i18n.translate('xpack.observability.slos.slo.item.actions.clone', { + {i18n.translate('xpack.observability.slo.slo.item.actions.clone', { defaultMessage: 'Clone', })} , @@ -168,7 +185,7 @@ export function SloListItem({ onClick={handleDelete} data-test-subj="sloActionsDelete" > - {i18n.translate('xpack.observability.slos.slo.item.actions.delete', { + {i18n.translate('xpack.observability.slo.slo.item.actions.delete', { defaultMessage: 'Delete', })} , diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx index 85ce283c90b18..cf95af3101c13 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx @@ -41,7 +41,7 @@ export function SloListItems({ sloList, loading, error }: Props) { diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx index 0e7ccc31d54ee..ebc01f94a99c2 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx @@ -41,14 +41,14 @@ export type Item = EuiSelectableOption & { const SORT_OPTIONS: Array> = [ { - label: i18n.translate('xpack.observability.slos.list.sortBy.name', { + label: i18n.translate('xpack.observability.slo.list.sortBy.name', { defaultMessage: 'Name', }), type: 'name', checked: 'on', }, { - label: i18n.translate('xpack.observability.slos.list.sortBy.indicatorType', { + label: i18n.translate('xpack.observability.slo.list.sortBy.indicatorType', { defaultMessage: 'Indicator type', }), type: 'indicatorType', @@ -57,19 +57,19 @@ const SORT_OPTIONS: Array> = [ const INDICATOR_TYPE_OPTIONS: Array> = [ { - label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.apmLatency', { + label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.apmLatency', { defaultMessage: 'APM latency', }), type: 'sli.apm.transactionDuration', }, { - label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.apmAvailability', { + label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.apmAvailability', { defaultMessage: 'APM availability', }), type: 'sli.apm.transactionErrorRate', }, { - label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.customKql', { + label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.customKql', { defaultMessage: 'Custom KQL', }), type: 'sli.kql.custom', @@ -121,7 +121,7 @@ export function SloListSearchFilterSortBar({ fullWidth isLoading={loading} onChange={onChangeQuery} - placeholder={i18n.translate('xpack.observability.slos.list.search', { + placeholder={i18n.translate('xpack.observability.slo.list.search', { defaultMessage: 'Search', })} /> @@ -137,7 +137,7 @@ export function SloListSearchFilterSortBar({ isSelected={isFilterPopoverOpen} numFilters={selectedIndicatorTypeFilter.length} > - {i18n.translate('xpack.observability.slos.list.indicatorTypeFilter', { + {i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', { defaultMessage: 'Indicator type', })} @@ -149,7 +149,7 @@ export function SloListSearchFilterSortBar({ >

    - {i18n.translate('xpack.observability.slos.list.indicatorTypeFilter', { + {i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', { defaultMessage: 'Indicator type', })} @@ -173,7 +173,7 @@ export function SloListSearchFilterSortBar({ onClick={handleToggleSortButton} isSelected={isSortPopoverOpen} > - {i18n.translate('xpack.observability.slos.list.sortByType', { + {i18n.translate('xpack.observability.slo.list.sortByType', { defaultMessage: 'Sort by {type}', values: { type: selectedSort?.label.toLowerCase() || '' }, })} @@ -186,7 +186,7 @@ export function SloListSearchFilterSortBar({ >
    - {i18n.translate('xpack.observability.slos.list.sortBy', { + {i18n.translate('xpack.observability.slo.list.sortBy', { defaultMessage: 'Sort by', })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx index 5231edb3d945c..8ab49befd627c 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx @@ -45,7 +45,7 @@ export function SloListWelcomePrompt() { title={

    - {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.title', { + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.title', { defaultMessage: 'Track and deliver on your SLOs', })}

    @@ -58,14 +58,14 @@ export function SloListWelcomePrompt() { body={ <>

    - {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.messageParagraph1', { + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.messageParagraph1', { defaultMessage: 'Measure key metrics important to the business, such as service-level indicators and service-level objectives (SLIs/SLOs) to deliver on SLAs.', })}

    - {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.messageParagraph2', { + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.messageParagraph2', { defaultMessage: 'Easily report the uptime and reliability of your services to stakeholders with real-time insights.', })} @@ -81,7 +81,7 @@ export function SloListWelcomePrompt() { {i18n.translate( - 'xpack.observability.slos.sloList.welcomePrompt.getStartedMessage', + 'xpack.observability.slo.sloList.welcomePrompt.getStartedMessage', { defaultMessage: 'To get started, create your first SLO.', } @@ -93,12 +93,9 @@ export function SloListWelcomePrompt() { - {i18n.translate( - 'xpack.observability.slos.sloList.welcomePrompt.buttonLabel', - { - defaultMessage: 'Create SLO', - } - )} + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.buttonLabel', { + defaultMessage: 'Create SLO', + })} @@ -109,7 +106,7 @@ export function SloListWelcomePrompt() { {i18n.translate( - 'xpack.observability.slos.sloList.welcomePrompt.needLicenseMessage', + 'xpack.observability.slo.sloList.welcomePrompt.needLicenseMessage', { defaultMessage: 'You need an Elastic Cloud subscription or Platinum license to use SLOs.', @@ -129,7 +126,7 @@ export function SloListWelcomePrompt() { data-test-subj="slosPageWelcomePromptSignupForCloudButton" > {i18n.translate( - 'xpack.observability.slos.sloList.welcomePrompt.signupForCloud', + 'xpack.observability.slo.sloList.welcomePrompt.signupForCloud', { defaultMessage: 'Sign up for Elastic Cloud', } @@ -144,7 +141,7 @@ export function SloListWelcomePrompt() { data-test-subj="slosPageWelcomePromptSignupForLicenseButton" > {i18n.translate( - 'xpack.observability.slos.sloList.welcomePrompt.signupForLicense', + 'xpack.observability.slo.sloList.welcomePrompt.signupForLicense', { defaultMessage: 'Sign up for license', } @@ -161,14 +158,14 @@ export function SloListWelcomePrompt() { <> - {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.learnMore', { + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.learnMore', { defaultMessage: 'Want to learn more?', })}   - {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.learnMoreLink', { + {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.learnMoreLink', { defaultMessage: 'Read the docs', })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx index ed5935139e15f..943b7f9742c58 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx @@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { asPercentWithTwoDecimals } from '../../../../common/utils/formatters'; import { SloSparkline } from './slo_sparkline'; @@ -22,15 +23,8 @@ export interface Props { export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoading }: Props) { const isSloFailed = slo.summary.status === 'VIOLATED' || slo.summary.status === 'DEGRADING'; const titleColor = isSloFailed ? 'danger' : ''; - - const historicalSliData = historicalSummary.map((data) => ({ - key: new Date(data.date).getTime(), - value: data.status === 'NO_DATA' ? undefined : data.sliValue, - })); - const errorBudgetBurnDownData = historicalSummary.map((data) => ({ - key: new Date(data.date).getTime(), - value: data.status === 'NO_DATA' ? undefined : data.errorBudget.remaining, - })); + const errorBudgetBurnDownData = formatHistoricalData(historicalSummary, 'error_budget_remaining'); + const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value'); return ( @@ -38,7 +32,7 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi { expect(screen.getByText('Create new SLO')).toBeTruthy(); }); + it('should have an Auto Refresh button', async () => { + useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: historicalSummaryData, + }); + + await act(async () => { + render(, config); + }); + + expect(screen.getByTestId('autoRefreshButton')).toBeTruthy(); + }); + describe('when API has returned results', () => { it('renders the SLO list with SLO items', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); diff --git a/x-pack/plugins/observability/public/pages/slos/index.tsx b/x-pack/plugins/observability/public/pages/slos/index.tsx index 0fd7b51810d5e..c0a51bf2cf0c0 100644 --- a/x-pack/plugins/observability/public/pages/slos/index.tsx +++ b/x-pack/plugins/observability/public/pages/slos/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -17,6 +17,7 @@ import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; import { SloListWelcomePrompt } from './components/slo_list_welcome_prompt'; +import { AutoRefreshButton } from './components/auto_refresh_button'; import PageNotFound from '../404'; import { paths } from '../../config'; import { isSloFeatureEnabled } from './helpers/is_slo_feature_enabled'; @@ -35,6 +36,8 @@ export function SlosPage() { const { total } = sloList || {}; + const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + useBreadcrumbs([ { href: basePath.prepend(paths.observability.slos), @@ -48,6 +51,10 @@ export function SlosPage() { navigateToUrl(basePath.prepend(paths.observability.sloCreate)); }; + const handleToggleAutoRefresh = () => { + setIsAutoRefreshing(!isAutoRefreshing); + }; + if (!isSloFeatureEnabled(config)) { return ; } @@ -74,16 +81,20 @@ export function SlosPage() { fill onClick={handleClickCreateSlo} > - {i18n.translate('xpack.observability.slos.sloList.pageHeader.createNewButtonLabel', { + {i18n.translate('xpack.observability.slo.sloList.pageHeader.createNewButtonLabel', { defaultMessage: 'Create new SLO', })} , + , ], bottomBorder: false, }} data-test-subj="slosPage" > - + ); } diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index e43725d0c5681..6af40e9a4345f 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -105,7 +105,7 @@ export interface ObservabilityPublicPluginsStart { ruleTypeRegistry: RuleTypeRegistryContract; security: SecurityPluginStart; share: SharePluginStart; - spaces: SpacesPluginStart; + spaces?: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; unifiedSearch: UnifiedSearchPublicPluginStart; diff --git a/x-pack/plugins/observability/public/typings/slo/index.ts b/x-pack/plugins/observability/public/typings/slo/index.ts index 677ec38f84c53..180a92c6f77a2 100644 --- a/x-pack/plugins/observability/public/typings/slo/index.ts +++ b/x-pack/plugins/observability/public/typings/slo/index.ts @@ -22,4 +22,9 @@ interface BurnRateRuleParams extends RuleTypeParams { shortWindow: Duration; } -export type { BurnRateRuleParams, Duration, DurationUnit }; +interface ChartData { + key: number; + value: number | undefined; +} + +export type { BurnRateRuleParams, ChartData, Duration, DurationUnit }; diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts index 00552c0adc6f1..0c9ad40784ec8 100644 --- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts +++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts @@ -13,6 +13,7 @@ import type { DataViewSpec, } from '@kbn/data-views-plugin/public'; import { RuntimeField } from '@kbn/data-views-plugin/public'; +import { DataViewMissingIndices } from '@kbn/data-views-plugin/common'; import { DataTypesLabels } from '../../components/shared/exploratory_view/labels'; import { syntheticsRuntimeFields } from '../../components/shared/exploratory_view/configurations/synthetics/runtime_fields'; import { getApmDataViewTitle } from '../../components/shared/exploratory_view/utils/utils'; @@ -121,25 +122,33 @@ export class ObservabilityDataViews { const { runtimeFields } = getFieldFormatsForApp(app); - const dataView = await this.dataViews.create( - { - title: appIndicesPattern, - id: getAppDataViewId(app, indices), - timeFieldName: '@timestamp', - fieldFormats: this.getFieldFormats(app), - name: DataTypesLabels[app], - }, - false, - false - ); - - if (runtimeFields !== null) { - runtimeFields.forEach(({ name, field }) => { - dataView.addRuntimeField(name, field); - }); - } + const id = getAppDataViewId(app, indices); + + try { + const dataView = await this.dataViews.create( + { + id, + title: appIndicesPattern, + timeFieldName: '@timestamp', + fieldFormats: this.getFieldFormats(app), + name: DataTypesLabels[app], + }, + false, + false + ); - return dataView; + if (runtimeFields !== null) { + runtimeFields.forEach(({ name, field }) => { + dataView.addRuntimeField(name, field); + }); + } + + return dataView; + } catch (e) { + if (e instanceof DataViewMissingIndices) { + this.dataViews.clearInstanceCache(id); + } + } } async createAndSavedDataView(app: AppDataType, indices: string) { diff --git a/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts b/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts new file mode 100644 index 0000000000000..40625fbdee0cb --- /dev/null +++ b/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FetchHistoricalSummaryResponse } from '@kbn/slo-schema'; + +import { ChartData } from '../../typings/slo'; + +type DataType = 'error_budget_remaining' | 'error_budget_consumed' | 'sli_value'; + +export function formatHistoricalData( + historicalSummary: FetchHistoricalSummaryResponse[string] | undefined, + dataType: DataType +): ChartData[] { + function getDataValue(data: FetchHistoricalSummaryResponse[string][number]) { + switch (dataType) { + case 'error_budget_consumed': + return data.errorBudget.consumed; + case 'error_budget_remaining': + return data.errorBudget.remaining; + default: + return data.sliValue; + } + } + + if (!historicalSummary) { + return []; + } + + return historicalSummary.map((data) => ({ + key: new Date(data.date).getTime(), + value: data.status === 'NO_DATA' ? undefined : getDataValue(data), + })); +} diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index 6a2351435e9aa..4f01d59f70174 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -38,7 +38,7 @@ export function sloBurnRateRuleType(createLifecycleRuleExecutor: CreateLifecycle defaultActionGroupId: FIRED_ACTION.id, actionGroups: [FIRED_ACTION], producer: 'slo', - minimumLicenseRequired: 'basic' as LicenseType, + minimumLicenseRequired: 'platinum' as LicenseType, isExportable: true, executor: createLifecycleRuleExecutor(getRuleExecutor()), doesSetRecoveryContext: true, diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 39a69d7fa1666..4b2e2c722230b 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -13,10 +13,15 @@ import { DEFAULT_APP_CATEGORIES, Logger, } from '@kbn/core/server'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { PluginSetupContract } from '@kbn/alerting-plugin/server'; import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; -import { createUICapabilities } from '@kbn/cases-plugin/common'; +import { + createUICapabilities as createCasesUICapabilities, + getApiTags as getCasesApiTags, +} from '@kbn/cases-plugin/common'; +import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; @@ -50,6 +55,7 @@ interface PluginSetup { features: FeaturesSetup; guidedOnboarding: GuidedOnboardingPluginSetup; ruleRegistry: RuleRegistryPluginSetupContract; + spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; } @@ -62,7 +68,9 @@ export class ObservabilityPlugin implements Plugin { } public setup(core: CoreSetup, plugins: PluginSetup) { - const casesCapabilities = createUICapabilities(); + const casesCapabilities = createCasesUICapabilities(); + const casesApiTags = getCasesApiTags(observabilityFeatureId); + const config = this.initContext.config.get(); plugins.features.registerKibanaFeature({ @@ -77,7 +85,7 @@ export class ObservabilityPlugin implements Plugin { cases: [observabilityFeatureId], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.all, app: [casesFeatureId, 'kibana'], catalogue: [observabilityFeatureId], cases: { @@ -87,13 +95,13 @@ export class ObservabilityPlugin implements Plugin { push: [observabilityFeatureId], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.read, app: [casesFeatureId, 'kibana'], catalogue: [observabilityFeatureId], cases: { @@ -101,7 +109,7 @@ export class ObservabilityPlugin implements Plugin { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.read, }, @@ -116,7 +124,7 @@ export class ObservabilityPlugin implements Plugin { groupType: 'independent', privileges: [ { - api: [], + api: casesApiTags.delete, id: 'cases_delete', name: i18n.translate( 'xpack.observability.featureRegistry.deleteSubFeatureDetails', diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts index ba9856860ec84..fa34496573338 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts @@ -13,7 +13,9 @@ describe('common', () => { ['foo-*', 'foo-*'], ['foo-*,bar-*', ['foo-*', 'bar-*']], ['remote:foo-*', 'remote:foo-*'], - ['remote:foo*,bar-*', ['remote:foo*', 'remote:bar-*']], + ['remote:foo*,bar-*', ['remote:foo*', 'bar-*']], + ['remote:foo*,remote:bar-*', ['remote:foo*', 'remote:bar-*']], + ['remote:foo*,bar-*,remote:baz-*', ['remote:foo*', 'bar-*', 'remote:baz-*']], ])("parses the index '%s' correctly", (index, expected) => { expect(parseIndex(index)).toEqual(expected); }); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts index 54197076d359f..ea64f23662695 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts @@ -17,15 +17,9 @@ export function getElastichsearchQueryOrThrow(kuery: string) { } export function parseIndex(index: string): string | string[] { - if (index.indexOf(',') > -1) { - if (index.indexOf(':') > -1) { - const indexParts = index.split(':'); // "remote_name:foo-*,bar*" - const remoteName = indexParts[0]; - return indexParts[1].split(',').map((idx) => `${remoteName}:${idx}`); // [ "remote_name:foo-*", "remote_name:bar-*"] - } - - return index.split(','); + if (index.indexOf(',') === -1) { + return index; } - return index; + return index.split(','); } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index eb85b5fb25b26..eadf340054f52 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -71,6 +71,7 @@ "@kbn/shared-ux-router", "@kbn/alerts-ui-shared", "@kbn/core-application-browser", + "@kbn/files-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/profiling/public/app.tsx b/x-pack/plugins/profiling/public/app.tsx index bf3a35d060f05..bf2f53aed0b0b 100644 --- a/x-pack/plugins/profiling/public/app.tsx +++ b/x-pack/plugins/profiling/public/app.tsx @@ -22,6 +22,7 @@ import { profilingRouter } from './routing'; import { Services } from './services'; import { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types'; import { ProfilingHeaderActionMenu } from './components/profiling_header_action_menu'; +import { RouterErrorBoundary } from './routing/router_error_boundary'; interface Props { profilingFetchServices: Services; @@ -82,21 +83,23 @@ function App({ - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx new file mode 100644 index 0000000000000..355ee1bd42f79 --- /dev/null +++ b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx @@ -0,0 +1,71 @@ +/* + * 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 { NotFoundRouteException } from '@kbn/typed-react-router-config'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found'; +import { useLocation } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { ProfilingPluginPublicStartDeps } from '../types'; + +export function RouterErrorBoundary({ children }: { children?: React.ReactNode }) { + const location = useLocation(); + return {children}; +} + +class ErrorBoundary extends React.Component<{ children?: React.ReactNode }, { error?: Error }, {}> { + public state: { error?: Error } = { + error: undefined, + }; + + static getDerivedStateFromError(error: Error) { + return { error }; + } + + render() { + if (this.state.error) { + return ; + } + + return this.props.children; + } +} + +const pageHeader = { + pageTitle: i18n.translate('xpack.profiling.universalProfiling', { + defaultMessage: 'Universal Profiling', + }), +}; + +function ErrorWithTemplate({ error }: { error: Error }) { + const { services } = useKibana(); + const { observability } = services; + + const ObservabilityPageTemplate = observability.navigation.PageTemplate; + + if (error instanceof NotFoundRouteException) { + return ( + + + + ); + } + + return ( + + + + + + ); +} + +function DummyComponent({ error }: { error: Error }) { + throw error; + return

    ; +} diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json index b1044792b3209..6364139982415 100644 --- a/x-pack/plugins/profiling/tsconfig.json +++ b/x-pack/plugins/profiling/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/core-http-request-handler-context-server", "@kbn/spaces-plugin", "@kbn/cloud-plugin", + "@kbn/shared-ux-prompt-not-found", // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index 2902533c145f1..8019e5bf3254a 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -1015,7 +1015,7 @@ describe('createLifecycleExecutor', () => { return { state }; }); - await executor( + const serializedAlerts = await executor( createDefaultAlertExecutorOptions({ alertId: 'TEST_ALERT_0', params: {}, @@ -1061,6 +1061,43 @@ describe('createLifecycleExecutor', () => { }) ); + expect(serializedAlerts.state.trackedAlerts).toEqual({ + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + flapping: true, + flappingHistory: flapping.slice(1).concat([false]), + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + flapping: false, + flappingHistory: [false, false, false], + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + TEST_ALERT_2: { + alertId: 'TEST_ALERT_2', + alertUuid: 'TEST_ALERT_2_UUID', + flapping: true, + flappingHistory: flapping.slice(1).concat([false]), + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_3: { + alertId: 'TEST_ALERT_3', + alertUuid: 'TEST_ALERT_3_UUID', + flapping: true, + flappingHistory: [false, false, false], + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + }); + + expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({}); + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( expect.objectContaining({ body: [ @@ -1070,7 +1107,7 @@ describe('createLifecycleExecutor', () => { [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', [ALERT_WORKFLOW_STATUS]: 'closed', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, - [ALERT_FLAPPING]: true, + [ALERT_FLAPPING]: false, [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), @@ -1182,7 +1219,7 @@ describe('createLifecycleExecutor', () => { return { state }; }); - await executor( + const serializedAlerts = await executor( createDefaultAlertExecutorOptions({ alertId: 'TEST_ALERT_0', params: {}, @@ -1228,6 +1265,44 @@ describe('createLifecycleExecutor', () => { }) ); + expect(serializedAlerts.state.trackedAlerts).toEqual({ + TEST_ALERT_2: { + alertId: 'TEST_ALERT_2', + alertUuid: 'TEST_ALERT_2_UUID', + flapping: true, + flappingHistory: [true, true, true], + pendingRecoveredCount: 1, + started: '2020-01-02T12:00:00.000Z', + }, + }); + + expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({ + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + flapping: true, + flappingHistory: [true, true, true, true, true], + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + flapping: false, + flappingHistory: notFlapping.slice(0, notFlapping.length - 1).concat([true]), + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + TEST_ALERT_3: { + alertId: 'TEST_ALERT_3', + alertUuid: 'TEST_ALERT_3_UUID', + flapping: false, + flappingHistory: notFlapping.slice(0, notFlapping.length - 1).concat([true]), + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + }); + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( expect.objectContaining({ body: expect.arrayContaining([ @@ -1235,10 +1310,10 @@ describe('createLifecycleExecutor', () => { { index: { _id: 'TEST_ALERT_0_UUID' } }, expect.objectContaining({ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', - [ALERT_STATUS]: ALERT_STATUS_ACTIVE, - [EVENT_ACTION]: 'active', + [ALERT_STATUS]: ALERT_STATUS_RECOVERED, + [EVENT_ACTION]: 'close', [EVENT_KIND]: 'signal', - [ALERT_FLAPPING]: true, + [ALERT_FLAPPING]: false, }), { index: { _id: 'TEST_ALERT_1_UUID' } }, expect.objectContaining({ diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index fdb73c85cecb1..c5387f66d000b 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -278,12 +278,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { - alertUuid, - started, - flapping: isCurrentlyFlapping, - pendingRecoveredCount, - } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -294,8 +289,6 @@ export const createLifecycleExecutor = pendingRecoveredCount: 0, }; - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - const event: ParsedTechnicalFields & ParsedExperimentalFields = { ...alertData?.fields, ...commonRuleFields, @@ -368,10 +361,11 @@ export const createLifecycleExecutor = const nextTrackedAlerts = Object.fromEntries( allEventsToIndex .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map(({ event, flappingHistory, flapping, pendingRecoveredCount }) => { + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[ALERT_INSTANCE_ID]!; const alertUuid = event[ALERT_UUID]!; const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, @@ -389,10 +383,11 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map(({ event, flappingHistory, flapping, pendingRecoveredCount }) => { + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[ALERT_INSTANCE_ID]!; const alertUuid = event[ALERT_UUID]!; const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 55b802ae06d8d..5c903966bd0cc 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -91,6 +91,11 @@ export enum SecurityPageName { cloudSecurityPostureDashboard = 'cloud_security_posture-dashboard', cloudSecurityPostureFindings = 'cloud_security_posture-findings', cloudSecurityPostureRules = 'cloud_security_posture-rules', + /* + * Warning: Computed values are not permitted in an enum with string valued members + * All cloud defend page names must match `CloudDefendPageId` in x-pack/plugins/cloud_defend/public/common/navigation/types.ts + */ + cloudDefendPolicies = 'cloud_defend-policies', dashboardsLanding = 'dashboards', dataQuality = 'data_quality', detections = 'detections', diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index cb4848d79eb41..2c2a4fb23e859 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -220,9 +220,9 @@ export class EndpointActionGenerator extends BaseDataGenerator { ResponseActionsExecuteParameters > ).parameters = { - command: (overrides.parameters as ResponseActionsExecuteParameters).command ?? 'ls -al', + command: (overrides.parameters as ResponseActionsExecuteParameters)?.command ?? 'ls -al', timeout: - (overrides.parameters as ResponseActionsExecuteParameters).timeout ?? + (overrides.parameters as ResponseActionsExecuteParameters)?.timeout ?? DEFAULT_EXECUTE_ACTION_TIMEOUT, // 4hrs }; } diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts index f411b5e229590..663317f334751 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts @@ -117,7 +117,7 @@ describe('Related integrations', () => { const rule = { name: 'Related integrations rule', integrations: [ - { name: 'Amazon CloudFront', installed: true, enabled: true }, + { name: 'AWS Cloudfront', installed: true, enabled: true }, { name: 'AWS CloudTrail', installed: true, enabled: false }, { name: 'Aws Unknown', installed: false, enabled: false }, { name: 'System', installed: true, enabled: true }, 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 213ea64fc4ceb..41b190c8ccc0d 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 @@ -86,7 +86,10 @@ describe('Exceptions Table', () => { expectedExportedExceptionList(this.exceptionListResponse) ); - cy.get(TOASTER).should('have.text', 'Exception list export success'); + cy.get(TOASTER).should( + 'have.text', + `Exception list "${getExceptionList1().name}" exported successfully` + ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts index d571d7cd4fae8..99d38ef3c5819 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts @@ -83,7 +83,10 @@ describe('Exceptions Table', () => { expectedExportedExceptionList(this.exceptionListResponse) ); - cy.get(TOASTER).should('have.text', 'Exception list export success'); + cy.get(TOASTER).should( + 'have.text', + `Exception list "${getExceptionList1().name}" exported successfully` + ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts index 1daaa206a67f4..0cbd85ffcba43 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts @@ -274,10 +274,8 @@ describe('url state', () => { ); }); - // Failing on `main`, skipping for now, to be addressed by security-detection-rules-area - it.skip('Do not clears kql when navigating to a new page', () => { + it('Do not clears kql when navigating to a new page', () => { visitWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); - kqlSearch('source.ip: "10.142.0.9"{enter}'); navigateFromHeaderTo(NETWORK); cy.get(KQL_INPUT).should('have.text', 'source.ip: "10.142.0.9"'); }); diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc index 6f6c371f5f27f..85418bdeb31b7 100644 --- a/x-pack/plugins/security_solution/kibana.jsonc +++ b/x-pack/plugins/security_solution/kibana.jsonc @@ -15,6 +15,7 @@ "alerting", "cases", "cloud", + "cloudDefend", "cloudSecurityPosture", "dashboard", "data", diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 9853c52502fb2..87d43742a9433 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -7,7 +7,8 @@ import { i18n } from '@kbn/i18n'; -import { getSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public'; +import { getSecuritySolutionLink as getCloudDefendSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public'; +import { getSecuritySolutionLink as getCloudPostureSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public'; import { getSecuritySolutionDeepLink } from '@kbn/threat-intelligence-plugin/public'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; import { getCasesDeepLinks } from '@kbn/cases-plugin/public'; @@ -167,7 +168,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], }, { - ...getSecuritySolutionLink('dashboard'), + ...getCloudPostureSecuritySolutionLink('dashboard'), features: [FEATURE.general], }, { @@ -251,7 +252,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], }, { - ...getSecuritySolutionLink('findings'), + ...getCloudPostureSecuritySolutionLink('findings'), features: [FEATURE.general], navLinkStatus: AppNavLinkStatus.visible, order: 9002, @@ -529,7 +530,10 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ path: RESPONSE_ACTIONS_HISTORY_PATH, }, { - ...getSecuritySolutionLink('benchmarks'), + ...getCloudPostureSecuritySolutionLink('benchmarks'), + }, + { + ...getCloudDefendSecuritySolutionLink('policies'), }, ], }, diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts index bbff6ffa0a6f9..d9a988004ac1a 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -7,6 +7,7 @@ import { getSecuritySolutionNavTab as getSecuritySolutionCSPNavTab } from '@kbn/cloud-security-posture-plugin/public'; import { getSecuritySolutionNavTab as getSecuritySolutionTINavTab } from '@kbn/threat-intelligence-plugin/public'; +import { getSecuritySolutionNavTab as getSecuritySolutionCloudDefendNavTab } from '@kbn/cloud-defend-plugin/public'; import * as i18n from '../translations'; import type { SecurityNav, SecurityNavGroup } from '../../common/components/navigation/types'; import { SecurityNavGroupKey } from '../../common/components/navigation/types'; @@ -186,6 +187,10 @@ export const navTabs: SecurityNav = { ...getSecuritySolutionCSPNavTab('benchmarks', APP_PATH), urlKey: 'administration', }, + [SecurityPageName.cloudDefendPolicies]: { + ...getSecuritySolutionCloudDefendNavTab('policies', APP_PATH), + urlKey: 'administration', + }, [SecurityPageName.entityAnalytics]: { id: SecurityPageName.entityAnalytics, name: i18n.ENTITY_ANALYTICS, diff --git a/x-pack/plugins/security_solution/public/cloud_defend/index.ts b/x-pack/plugins/security_solution/public/cloud_defend/index.ts new file mode 100644 index 0000000000000..4ec2329d36bd5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/index.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 { SecuritySubPlugin } from '../app/types'; +import { routes } from './routes'; + +export class CloudDefend { + public setup() {} + + public start(): SecuritySubPlugin { + return { routes }; + } +} diff --git a/x-pack/plugins/security_solution/public/cloud_defend/links.ts b/x-pack/plugins/security_solution/public/cloud_defend/links.ts new file mode 100644 index 0000000000000..652ebe6181151 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/links.ts @@ -0,0 +1,26 @@ +/* + * 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 { getSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public'; +import { i18n } from '@kbn/i18n'; +import type { SecurityPageName } from '../../common/constants'; +import { SERVER_APP_ID } from '../../common/constants'; +import type { LinkItem } from '../common/links/types'; +import { IconCloudDefend } from '../management/icons/cloud_defend'; + +const commonLinkProperties: Partial = { + hideTimeline: true, + capabilities: [`${SERVER_APP_ID}.show`], +}; + +export const manageLinks: LinkItem = { + ...getSecuritySolutionLink('policies'), + description: i18n.translate('xpack.securitySolution.appLinks.cloudDefendPoliciesDescription', { + defaultMessage: 'View drift prevention policies.', + }), + landingIcon: IconCloudDefend, + ...commonLinkProperties, +}; diff --git a/x-pack/plugins/security_solution/public/cloud_defend/routes.tsx b/x-pack/plugins/security_solution/public/cloud_defend/routes.tsx new file mode 100644 index 0000000000000..18bd7641addf3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/routes.tsx @@ -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 React from 'react'; +import type { + CloudDefendPageId, + CloudDefendSecuritySolutionContext, +} from '@kbn/cloud-defend-plugin/public'; +import { CLOUD_DEFEND_BASE_PATH } from '@kbn/cloud-defend-plugin/public'; +import type { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { useKibana } from '../common/lib/kibana'; +import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; +import { SpyRoute } from '../common/utils/route/spy_routes'; +import { FiltersGlobal } from '../common/components/filters_global'; +import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; + +// This exists only for the type signature cast +const CloudDefendSpyRoute = ({ pageName, ...rest }: { pageName?: CloudDefendPageId }) => ( + +); + +const cloudDefendSecuritySolutionContext: CloudDefendSecuritySolutionContext = { + getFiltersGlobalComponent: () => FiltersGlobal, + getSpyRouteComponent: () => CloudDefendSpyRoute, +}; + +const CloudDefend = () => { + const { cloudDefend } = useKibana().services; + const CloudDefendRouter = cloudDefend.getCloudDefendRouter(); + + return ( + + + + + + ); +}; + +CloudDefend.displayName = 'CloudDefend'; + +export const routes: SecuritySubPluginRoutes = [ + { + path: CLOUD_DEFEND_BASE_PATH, + component: CloudDefend, + }, +]; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts index def3b0ed9f5eb..e4c0dfd1f20db 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts @@ -51,6 +51,9 @@ export const manageCategories: LinkCategories = [ label: i18n.translate('xpack.securitySolution.appLinks.category.cloudSecurityPosture', { defaultMessage: 'CLOUD SECURITY POSTURE', }), - linkIds: [SecurityPageName.cloudSecurityPostureBenchmarks], + linkIds: [ + SecurityPageName.cloudSecurityPostureBenchmarks, + SecurityPageName.cloudDefendPolicies, + ], }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.test.ts b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.test.ts index 6b432edfb5bd1..376c7fbf66d39 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.test.ts @@ -306,7 +306,102 @@ describe('showToasterMessage', () => { expect(addSuccess).toHaveBeenNthCalledWith(2, 'Successfully imported 1 connector.'); expect(addError).not.toHaveBeenCalled(); }); + it('should display 1 error message has 2 invalid connectors in the title even when error array has one message but "id" field', () => { + const addError = jest.fn(); + const addSuccess = jest.fn(); + showToasterMessage({ + importResponse: { + success: false, + success_count: 1, + rules_count: 2, + action_connectors_success: false, + errors: [ + { + rule_id: 'rule_id', + error: { + status_code: 400, + message: 'an error message', + }, + }, + ], + action_connectors_errors: [ + { + rule_id: 'rule_id', + id: 'connector1,connector2', + error: { + status_code: 400, + message: 'an error message', + }, + }, + ], + exceptions_success: true, + exceptions_success_count: 0, + }, + exceptionsIncluded: false, + actionConnectorsIncluded: true, + successMessage: (msg) => `success: ${msg}`, + errorMessage: (msg) => `error: ${msg}`, + errorMessageDetailed: (msg) => `errorDetailed: ${msg}`, + addError, + addSuccess, + }); + + expect(addError).toHaveBeenCalledTimes(1); + + expect(addError).toHaveBeenCalledWith(new Error('errorDetailed: an error message'), { + title: 'Failed to import 2 connectors', + }); + expect(addSuccess).not.toHaveBeenCalled(); + }); + it('should display 1 error message has 1 invalid connectors in the title even when error array has one message but "id" field', () => { + const addError = jest.fn(); + const addSuccess = jest.fn(); + + showToasterMessage({ + importResponse: { + success: false, + success_count: 1, + rules_count: 2, + action_connectors_success: false, + errors: [ + { + rule_id: 'rule_id', + error: { + status_code: 400, + message: 'an error message', + }, + }, + ], + action_connectors_errors: [ + { + rule_id: 'rule_id', + id: 'connector1', + error: { + status_code: 400, + message: 'an error message', + }, + }, + ], + exceptions_success: true, + exceptions_success_count: 0, + }, + exceptionsIncluded: false, + actionConnectorsIncluded: true, + successMessage: (msg) => `success: ${msg}`, + errorMessage: (msg) => `error: ${msg}`, + errorMessageDetailed: (msg) => `errorDetailed: ${msg}`, + addError, + addSuccess, + }); + + expect(addError).toHaveBeenCalledTimes(1); + + expect(addError).toHaveBeenCalledWith(new Error('errorDetailed: an error message'), { + title: 'Failed to import 1 connector', + }); + expect(addSuccess).not.toHaveBeenCalled(); + }); it('should display 1 error message for rules and connectors even when both fail', () => { const addError = jest.fn(); const addSuccess = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts index 92c6f9f666e4b..4ab30388168df 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts @@ -33,8 +33,14 @@ export const formatError = ( const mapErrorMessageToUserMessage = ( actionConnectorsErrors: Array ) => { - return actionConnectorsErrors.map((connectorError) => { - const { error } = connectorError; + let concatenatedActionIds: string = ''; + const mappedErrors = actionConnectorsErrors.map((connectorError) => { + // Using "as ImportResponseError" because the "id" field belongs only to + // "ImportResponseError" and if the connectorError has the id we use it to get the + // number of failing connectors by spliting the unique the connectors ids. + const { id, error } = connectorError as ImportResponseError; + concatenatedActionIds = + concatenatedActionIds && concatenatedActionIds !== id ? `${concatenatedActionIds},${id}` : id; const { status_code: statusCode, message: originalMessage } = error || {}; let message; switch (statusCode) { @@ -47,9 +53,12 @@ const mapErrorMessageToUserMessage = ( break; } - return { ...connectorError, error: { ...error, message } }; }); + const actionIds: Set = new Set( + concatenatedActionIds && [...concatenatedActionIds.split(',')] + ); + return { mappedErrors, numberOfActions: actionIds.size }; }; export const showToasterMessage = ({ @@ -100,13 +109,13 @@ export const showToasterMessage = ({ importResponse.action_connectors_errors != null && importResponse.action_connectors_errors.length > 0 ) { - const userErrorMessages = mapErrorMessageToUserMessage( + const { mappedErrors: userErrorMessages, numberOfActions } = mapErrorMessageToUserMessage( importResponse.action_connectors_errors ); const connectorError = formatError(errorMessageDetailed, importResponse, userErrorMessages); return addError(connectorError, { - title: i18n.IMPORT_CONNECTORS_FAILED(userErrorMessages.length), + title: i18n.IMPORT_CONNECTORS_FAILED(numberOfActions || userErrorMessages.length), }); } const ruleError = formatError(errorMessageDetailed, importResponse, importResponse.errors); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index b0190d40ced78..29f69700afdb7 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -42,6 +42,7 @@ export type UrlStateType = | 'explore' | 'dashboards' | 'indicators' + | 'cloud_defend' | 'cloud_posture' | 'findings' | 'entity_analytics' @@ -84,6 +85,7 @@ export const securityNavKeys = [ SecurityPageName.cloudSecurityPostureDashboard, SecurityPageName.cloudSecurityPostureFindings, SecurityPageName.cloudSecurityPostureBenchmarks, + SecurityPageName.cloudDefendPolicies, SecurityPageName.entityAnalytics, SecurityPageName.dataQuality, ] as const; diff --git a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts index 72337661c6c38..f9bfcfc72c346 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts @@ -30,7 +30,7 @@ export const useTimelineSavePrompt = ( onAppLeave: (handler: AppLeaveHandler) => void ) => { const dispatch = useDispatch(); - const { overlays, application } = useKibana().services; + const { overlays, application, http } = useKibana().services; const getIsTimelineVisible = useShowTimelineForGivenPath(); const history = useHistory(); @@ -66,10 +66,12 @@ export const useTimelineSavePrompt = ( if (confirmRes) { unblock(); - - application.navigateToUrl(location.pathname + location.hash + location.search, { - state: location.state, - }); + application.navigateToUrl( + http.basePath.get() + location.pathname + location.hash + location.search, + { + state: location.state, + } + ); } else { showSaveTimelineModal(); } @@ -92,6 +94,7 @@ export const useTimelineSavePrompt = ( }; }, [ history, + http.basePath, application, overlays, showSaveTimelineModal, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index fae192a479de4..41e0c8717a1fa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -535,12 +535,16 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ newCommentValue={newComment} newCommentOnChange={setComment} /> - - + {listType !== ExceptionListTypeEnum.ENDPOINT && ( + <> + + + + )} {showAlertCloseOptions && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx index adcbd7ac36b5f..c0cbe1ac5caf4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx @@ -229,7 +229,7 @@ const ExceptionsViewerComponent = ({ ); const exceptionListFilter = useMemo(() => { - if (exceptionsToShow.active && exceptionsToShow.expired) { + if (isEndpointSpecified || (exceptionsToShow.active && exceptionsToShow.expired)) { return undefined; } const savedObjectPrefixes = getSavedObjectTypes({ @@ -241,7 +241,7 @@ const ExceptionsViewerComponent = ({ if (exceptionsToShow.expired) { return buildShowExpiredExceptionsFilter(savedObjectPrefixes); } - }, [exceptionsToShow, namespaceTypes]); + }, [exceptionsToShow, namespaceTypes, isEndpointSpecified]); const handleFetchItems = useCallback( async (options?: GetExceptionItemProps) => { @@ -516,6 +516,7 @@ const ExceptionsViewerComponent = ({ exceptionsToShow={exceptionsToShow} onChangeExceptionsToShow={handleExceptionsToShow} lastUpdated={lastUpdated} + isEndpoint={isEndpointSpecified} /> { exceptionsToShow={{ active: true }} onChangeExceptionsToShow={(optionId: string) => {}} lastUpdated={1660534202} + isEndpoint={false} /> ); @@ -47,6 +48,7 @@ describe('ExceptionsViewerUtility', () => { exceptionsToShow={{ active: true }} onChangeExceptionsToShow={(optionId: string) => {}} lastUpdated={Date.now()} + isEndpoint={false} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx index d7ea7acfd5f31..f1b909b050f87 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx @@ -42,6 +42,7 @@ interface ExceptionsViewerUtilityProps { lastUpdated: string | number; exceptionsToShow: { [id: string]: boolean }; onChangeExceptionsToShow: (optionId: string) => void; + isEndpoint: boolean; } /** @@ -52,6 +53,7 @@ const ExceptionsViewerUtilityComponent: React.FC = lastUpdated, exceptionsToShow, onChangeExceptionsToShow, + isEndpoint, }): JSX.Element => { return ( @@ -88,22 +90,24 @@ const ExceptionsViewerUtilityComponent: React.FC = /> - + {!isEndpoint && ( + + )} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx index 04a350e328a26..1dd03d8bc9f9d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx @@ -400,12 +400,16 @@ const EditExceptionFlyoutComponent: React.FC = ({ newCommentValue={newComment} newCommentOnChange={setComment} /> - - + {listType !== ExceptionListTypeEnum.ENDPOINT && ( + <> + + + + )} {showAlertCloseOptions && ( <> 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 e70844348c385..4d1570ba3b491 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 @@ -66,11 +66,8 @@ export const enrichItemWithName = */ export const enrichItemWithExpireTime = (expireTimeToAdd: Moment | undefined) => - (items: ExceptionsBuilderReturnExceptionItem[]): ExceptionsBuilderReturnExceptionItem[] => { - return expireTimeToAdd != null - ? enrichNewExceptionItemsWithExpireTime(items, expireTimeToAdd) - : items; - }; + (items: ExceptionsBuilderReturnExceptionItem[]): ExceptionsBuilderReturnExceptionItem[] => + enrichNewExceptionItemsWithExpireTime(items, expireTimeToAdd); /** * Modifies item entries to be in correct format and adds os selection to items diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx index aaaffb0e70d2c..87d2e437baeed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx @@ -186,12 +186,13 @@ export const enrichNewExceptionItemsWithComments = ( */ export const enrichNewExceptionItemsWithExpireTime = ( exceptionItems: ExceptionsBuilderReturnExceptionItem[], - expireTime: Moment + expireTime: Moment | undefined ): ExceptionsBuilderReturnExceptionItem[] => { + const expireTimeDateString = expireTime !== undefined ? expireTime.toISOString() : undefined; return exceptionItems.map((item: ExceptionsBuilderReturnExceptionItem) => { return { ...item, - expire_time: expireTime.toISOString(), + expire_time: expireTimeDateString, }; }); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index 1eadf14b02b8c..0f93d66efbf6f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -124,7 +124,6 @@ const MetaRule = t.intersection([ }), ]); -// TODO: make a ticket export const RuleSchema = t.intersection([ t.type({ author: RuleAuthorArray, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx index dd173faade654..66d9d4bbf4e50 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx @@ -231,6 +231,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages; const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isPreflightInProgress; + const numberOfSelectedRules = isAllSelected ? pagination.total : selectedRuleIds?.length ?? 1; return ( <> @@ -275,7 +276,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { )} {isBulkEditFlyoutVisible && bulkEditActionType !== undefined && ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx index fe895b93feecb..392567dac1e39 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx @@ -19,6 +19,7 @@ import type { RawBucket, } from '@kbn/securitysolution-grouping'; import { getGrouping, isNoneGroup } from '@kbn/securitysolution-grouping'; +import type { AlertsGroupingAggregation } from './grouping_settings/types'; import { useGetGroupSelector } from '../../../common/containers/grouping/hooks/use_get_group_selector'; import type { Status } from '../../../../common/detection_engine/schemas/common'; import { defaultGroup } from '../../../common/store/grouping/defaults'; @@ -183,7 +184,10 @@ export const GroupedAlertsTableComponent: React.FC = request, response, setQuery: setAlertsQuery, - } = useQueryAlerts<{}, GroupingAggregation & GroupingFieldTotalAggregation>({ + } = useQueryAlerts< + {}, + GroupingAggregation & GroupingFieldTotalAggregation + >({ query: queryGroups, indexName: signalIndexName, queryName: ALERTS_QUERY_NAMES.ALERTS_GROUPING, @@ -236,12 +240,12 @@ export const GroupedAlertsTableComponent: React.FC = isNoneGroup(selectedGroup) ? renderChildComponent([]) : getGrouping({ - badgeMetricStats: (fieldBucket: RawBucket) => + badgeMetricStats: (fieldBucket: RawBucket) => getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket), - customMetricStats: (fieldBucket: RawBucket) => + customMetricStats: (fieldBucket: RawBucket) => getSelectedGroupCustomMetrics(selectedGroup, fieldBucket), data: alertsGroupsData?.aggregations, - groupPanelRenderer: (fieldBucket: RawBucket) => + groupPanelRenderer: (fieldBucket: RawBucket) => getSelectedGroupButtonContent(selectedGroup, fieldBucket), groupsSelector, inspectButton: inspect, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx index c75c7585a6860..091f8a43c3493 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx @@ -19,12 +19,16 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { isArray } from 'lodash/fp'; import React from 'react'; import type { RawBucket } from '@kbn/securitysolution-grouping'; +import type { AlertsGroupingAggregation } from './types'; import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers'; import type { GenericBuckets } from '../../../../../common/search_strategy'; import { PopoverItems } from '../../../../common/components/popover_items'; import { COLUMN_TAGS } from '../../../pages/detection_engine/rules/translations'; -export const getSelectedGroupButtonContent = (selectedGroup: string, bucket: RawBucket) => { +export const getSelectedGroupButtonContent = ( + selectedGroup: string, + bucket: RawBucket +) => { switch (selectedGroup) { case 'kibana.alert.rule.name': return isArray(bucket.key) ? ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx index d7f4d5d384560..a68185d5efd6a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx @@ -8,6 +8,7 @@ import { EuiIcon } from '@elastic/eui'; import React from 'react'; import type { RawBucket } from '@kbn/securitysolution-grouping'; +import type { AlertsGroupingAggregation } from './types'; import * as i18n from '../translations'; const getSingleGroupSeverity = (severity?: string) => { @@ -63,7 +64,10 @@ const multiSeverity = ( ); -export const getSelectedGroupBadgeMetrics = (selectedGroup: string, bucket: RawBucket) => { +export const getSelectedGroupBadgeMetrics = ( + selectedGroup: string, + bucket: RawBucket +) => { const defaultBadges = [ { title: i18n.STATS_GROUP_ALERTS, @@ -131,7 +135,10 @@ export const getSelectedGroupBadgeMetrics = (selectedGroup: string, bucket: RawB ]; }; -export const getSelectedGroupCustomMetrics = (selectedGroup: string, bucket: RawBucket) => { +export const getSelectedGroupCustomMetrics = ( + selectedGroup: string, + bucket: RawBucket +) => { const singleSeverityComponent = bucket.severitiesSubAggregation?.buckets && bucket.severitiesSubAggregation?.buckets?.length ? getSingleGroupSeverity(bucket.severitiesSubAggregation?.buckets[0].key.toString()) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts new file mode 100644 index 0000000000000..4bb336807e6cf --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.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 type { GenericBuckets } from '@kbn/securitysolution-grouping/src'; +// Elasticsearch returns `null` when a sub-aggregation cannot be computed +type NumberOrNull = number | null; +export interface AlertsGroupingAggregation { + alertsCount?: { + value?: NumberOrNull; + }; + severitiesSubAggregation?: { + buckets?: GenericBuckets[]; + }; + countSeveritySubAggregation?: { + value?: NumberOrNull; + }; + usersCountAggregation?: { + value?: NumberOrNull; + }; + hostsCountAggregation?: { + value?: NumberOrNull; + }; + rulesCountAggregation?: { + value?: NumberOrNull; + }; + ruleTags?: { + doc_count_error_upper_bound?: number; + sum_other_doc_count?: number; + buckets?: GenericBuckets[]; + }; + stackByMultipleFields1?: { + buckets?: GenericBuckets[]; + doc_count_error_upper_bound?: number; + sum_other_doc_count?: number; + }; +} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx index 4695e19d77126..6e5c5472820a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx @@ -57,5 +57,27 @@ describe('InvestigateInResolverAction', () => { expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy(); }); + + it('returns true for process event from sysmon via filebeat', () => { + const data: Ecs = { + _id: '1', + agent: { type: ['filebeat'] }, + event: { dataset: ['windows.sysmon_operational'] }, + process: { entity_id: ['always_unique'] }, + }; + + expect(isInvestigateInResolverActionEnabled(data)).toBeTruthy(); + }); + + it('returns false for process event from filebeat but not from sysmon', () => { + const data: Ecs = { + _id: '1', + agent: { type: ['filebeat'] }, + event: { dataset: ['windows.not_sysmon'] }, + process: { entity_id: ['always_unique'] }, + }; + + expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx index 365aac96c8f48..f6b328a63b2c5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx @@ -8,9 +8,20 @@ import { get } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => - (get(['agent', 'type', 0], ecsData) === 'endpoint' || - (get(['agent', 'type', 0], ecsData) === 'winlogbeat' && - get(['event', 'module', 0], ecsData) === 'sysmon')) && - get(['process', 'entity_id'], ecsData)?.length === 1 && - get(['process', 'entity_id', 0], ecsData) !== ''; +export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => { + const agentType = get(['agent', 'type', 0], ecsData); + const processEntityIds = get(['process', 'entity_id'], ecsData); + const firstProcessEntityId = get(['process', 'entity_id', 0], ecsData); + const eventModule = get(['event', 'module', 0], ecsData); + const eventDataStream = get(['event', 'dataset'], ecsData); + const datasetIncludesSysmon = + Array.isArray(eventDataStream) && + eventDataStream.some((datastream) => datastream.includes('windows.sysmon')); + const agentTypeIsEndpoint = agentType === 'endpoint'; + const agentTypeIsWinlogBeat = agentType === 'winlogbeat' && eventModule === 'sysmon'; + const isEndpointOrSysmonFromWinlogBeat = + agentTypeIsEndpoint || agentTypeIsWinlogBeat || datasetIncludesSysmon; + const hasProcessEntityId = + processEntityIds != null && processEntityIds.length === 1 && firstProcessEntityId !== ''; + return isEndpointOrSysmonFromWinlogBeat && hasProcessEntityId; +}; 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 54f2b0cf6c5d3..eb945e18e7761 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 @@ -50,11 +50,13 @@ interface ExceptionsListCardProps { id, includeExpiredExceptions, listId, + name, namespaceType, }: { id: string; includeExpiredExceptions: boolean; listId: string; + name: string; namespaceType: NamespaceType; }) => () => Promise; readOnly: boolean; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx index 20a4fd280beec..4a82c748da97e 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx @@ -55,7 +55,7 @@ export const ManageRules: FC = memo( > -

    {i18n.MANAGE_RULES_HEADER}

    +

    {i18n.LINK_RULES_HEADER}

    diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx index 5a8912a422f5c..27ce5f3604c2f 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx @@ -24,6 +24,7 @@ import { checkIfListCannotBeEdited } from '../../utils/list.utils'; interface ExportListAction { id: string; listId: string; + name: string; namespaceType: NamespaceType; includeExpiredExceptions: boolean; } @@ -42,6 +43,7 @@ export const useExceptionsListCard = ({ handleExport: ({ id, listId, + name, namespaceType, includeExpiredExceptions, }: ExportListAction) => () => Promise; @@ -123,7 +125,19 @@ export const useExceptionsListCard = ({ key: 'Export', icon: 'exportAction', label: i18n.EXPORT_EXCEPTION_LIST, - onClick: (e: React.MouseEvent) => setShowExportModal(true), + onClick: (e: React.MouseEvent) => { + if (listType === ExceptionListTypeEnum.ENDPOINT) { + handleExport({ + id: exceptionsList.id, + listId: exceptionsList.list_id, + name: exceptionsList.name, + namespaceType: exceptionsList.namespace_type, + includeExpiredExceptions: true, + })(); + } else { + setShowExportModal(true); + } + }, }, { key: 'Delete', @@ -139,10 +153,10 @@ export const useExceptionsListCard = ({ }, }, { - key: 'ManageRules', + key: 'LinkRules', icon: 'gear', disabled: listCannotBeEdited, - label: 'Manage Rules', + label: i18n.LINK_RULES_OVERFLOW_BUTTON_TITLE, onClick: (e: React.MouseEvent) => { handleManageRules(); }, @@ -151,11 +165,14 @@ export const useExceptionsListCard = ({ [ exceptionsList.id, exceptionsList.list_id, + exceptionsList.name, exceptionsList.namespace_type, handleDelete, setShowExportModal, listCannotBeEdited, handleManageRules, + handleExport, + listType, ] ); @@ -192,6 +209,7 @@ export const useExceptionsListCard = ({ handleExport({ id: exceptionsList.id, listId: exceptionsList.list_id, + name: exceptionsList.name, namespaceType: exceptionsList.namespace_type, includeExpiredExceptions, })(); diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts index 3359ccb760b05..2494a6c989791 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts @@ -175,7 +175,7 @@ export const useListDetailsView = (exceptionListId: string) => { onError: (error: Error) => handleErrorStatus(error), onSuccess: (blob) => { setExportedList(blob); - toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.list_id)); + toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.name)); }, }); } catch (error) { diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx index fc09a603b2e13..d4e2c8f4f0977 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx @@ -14,6 +14,7 @@ import { } from '@kbn/securitysolution-exception-list-components'; import { EuiLoadingContent } from '@elastic/eui'; import { useParams } from 'react-router-dom'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { SecurityPageName } from '../../../../common/constants'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal'; @@ -67,6 +68,14 @@ export const ListsDetailViewComponent: FC = () => { const onModalOpen = useCallback(() => setShowExportModal(true), [setShowExportModal]); + const handleExportList = useCallback(() => { + if (list?.type === ExceptionListTypeEnum.ENDPOINT) { + onExportList(true); + } else { + onModalOpen(); + } + }, [onModalOpen, list, onExportList]); + const detailsViewContent = useMemo(() => { if (viewerStatus === ViewerStatus.ERROR) return ; @@ -87,7 +96,7 @@ export const ListsDetailViewComponent: FC = () => { backOptions={headerBackOptions} securityLinkAnchorComponent={ListDetailsLinkAnchor} onEditListDetails={onEditListDetails} - onExportList={onModalOpen} + onExportList={handleExportList} onDeleteList={handleDelete} onManageRules={onManageRules} /> @@ -155,7 +164,7 @@ export const ListsDetailViewComponent: FC = () => { handleDelete, handleReferenceDelete, onModalClose, - onModalOpen, + handleExportList, ]); return ( <> diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 533f920a0b40c..fe2f2450a3900 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -183,9 +183,9 @@ export const SharedLists = React.memo(() => { ); const handleExportSuccess = useCallback( - (listId: string) => + (listId: string, name: string) => (blob: Blob): void => { - addSuccess(i18n.EXCEPTION_EXPORT_SUCCESS); + addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(name)); setExportDownload({ name: listId, blob }); }, [addSuccess] @@ -202,11 +202,13 @@ export const SharedLists = React.memo(() => { ({ id, listId, + name, namespaceType, includeExpiredExceptions, }: { id: string; listId: string; + name: string; namespaceType: NamespaceType; includeExpiredExceptions: boolean; }) => @@ -217,7 +219,7 @@ export const SharedLists = React.memo(() => { listId, namespaceType, onError: handleExportError, - onSuccess: handleExportSuccess(listId), + onSuccess: handleExportSuccess(listId, name), }); }, [exportExceptionList, handleExportError, handleExportSuccess] diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts index c839089d307a9..6d1f8c115fab3 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts @@ -94,12 +94,6 @@ export const EXCEPTION_ITEM_DELETE_TEXT = (itemName: string) => } ); -export const EXCEPTION_LIST_EXPORTED_SUCCESSFULLY = (listName: string) => - i18n.translate('xpack.securitySolution.exceptions.list.exported_successfully', { - values: { listName }, - defaultMessage: '{listName} exported successfully', - }); - export const EXCEPTION_LIST_DELETED_SUCCESSFULLY = (listName: string) => i18n.translate('xpack.securitySolution.exceptions.list.deleted_successfully', { values: { listName }, @@ -118,10 +112,17 @@ export const MANAGE_RULES_SAVE = i18n.translate( defaultMessage: 'Save', } ); -export const MANAGE_RULES_HEADER = i18n.translate( - 'xpack.securitySolution.exceptions.list.manage_rules_header', +export const LINK_RULES_HEADER = i18n.translate( + 'xpack.securitySolution.exceptions.list.link_rules_header', + { + defaultMessage: 'Link rules', + } +); + +export const LINK_RULES_OVERFLOW_BUTTON_TITLE = i18n.translate( + 'xpack.securitySolution.exceptions.list.link_rules_overflow_button_title', { - defaultMessage: 'Manage rules', + defaultMessage: 'Link rules', } ); diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index 7addaeb331247..6ab1ca6df8464 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts @@ -112,12 +112,11 @@ export const NO_LISTS_BODY = i18n.translate( } ); -export const EXCEPTION_EXPORT_SUCCESS = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess', - { - defaultMessage: 'Exception list export success', - } -); +export const EXCEPTION_LIST_EXPORTED_SUCCESSFULLY = (listName: string) => + i18n.translate('xpack.securitySolution.exceptions.list.export_success', { + values: { listName }, + defaultMessage: 'Exception list "{listName}" exported successfully', + }); export const EXCEPTION_EXPORT_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError', diff --git a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx index 1d93699ff1b47..66aefc6db3e08 100644 --- a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -20,6 +20,7 @@ import { Rules } from './rules'; import { Timelines } from './timelines'; import { Management } from './management'; import { LandingPages } from './landing_pages'; +import { CloudDefend } from './cloud_defend'; import { CloudSecurityPosture } from './cloud_security_posture'; import { ThreatIntelligence } from './threat_intelligence'; @@ -37,6 +38,7 @@ const subPluginClasses = { Timelines, Management, LandingPages, + CloudDefend, CloudSecurityPosture, ThreatIntelligence, }; diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx index 41eb4c3be7912..174496f1f3d21 100644 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx @@ -13,6 +13,10 @@ import { fireEvent, act } from '@testing-library/react'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; +import { initialUserPrivilegesState } from '../../../common/components/user_privileges/user_privileges_context'; + +jest.mock('../../../common/components/user_privileges'); describe('when using EffectedPolicySelect component', () => { const generator = new EndpointDocGenerator('effected-policy-select'); @@ -161,5 +165,44 @@ describe('when using EffectedPolicySelect component', () => { selectPerPolicy(); expect(queryByTestId('loading-spinner')).not.toBeNull(); }); + + it('should hide policy link when no policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: false, + canReadPolicyManagement: false, + }, + }); + const { queryByTestId } = render({ isGlobal: false }); + expect(queryByTestId('test-policyLink')).toBeNull(); + }); + + it('should show policy link when all policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: true, + canReadPolicyManagement: true, + }, + }); + const { getByTestId } = render({ isGlobal: false }); + expect(getByTestId('test-policyLink')); + }); + + it('should show policy link when read policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: false, + canReadPolicyManagement: true, + }, + }); + const { getByTestId } = render({ isGlobal: false }); + expect(getByTestId('test-policyLink')); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx index da5c1c1f68535..079ea01492dcc 100644 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx +++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import type { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; import type { PolicyData } from '../../../../common/endpoint/types'; import { LinkToApp } from '../../../common/components/endpoint/link_to_app'; import { getPolicyDetailPath } from '../../common/routing'; @@ -104,6 +105,7 @@ export const EffectedPolicySelect = memo( ...otherSelectableProps }) => { const { getAppUrl } = useAppUrl(); + const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges; const getTestId = useTestIdGenerator(dataTestSubj); @@ -145,25 +147,35 @@ export const EffectedPolicySelect = memo( data-test-subj={`policy-${policy.id}-checkbox`} /> ), - append: ( + append: canReadPolicyManagement ? ( - ), + ) : null, policy, checked: isPolicySelected.has(policy.id) ? 'on' : undefined, disabled: isGlobal || !isPlatinumPlus || disabled, 'data-test-subj': `policy-${policy.id}`, })) .sort(({ label: labelA }, { label: labelB }) => labelA.localeCompare(labelB)); - }, [disabled, getAppUrl, isGlobal, isPlatinumPlus, options, selected]); + }, [ + canReadPolicyManagement, + disabled, + getAppUrl, + getTestId, + isGlobal, + isPlatinumPlus, + options, + selected, + ]); const handleOnPolicySelectChange = useCallback< Required>['onChange'] diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx new file mode 100644 index 0000000000000..835e816f85f47 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx @@ -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 { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import type { + ActionDetails, + ResponseActionExecuteOutputContent, + ResponseActionsExecuteParameters, +} from '../../../../common/endpoint/types'; +import React from 'react'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { + ExecuteActionHostResponseOutput, + type ExecuteActionHostResponseOutputProps, +} from './execute_action_host_response_output'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; +import { getDeferred } from '../../mocks/utils'; +import { waitFor } from '@testing-library/react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +describe('When using the `ExecuteActionHostResponseOutput` component', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let renderProps: ExecuteActionHostResponseOutputProps; + let apiMocks: ReturnType; + + beforeEach(() => { + const appTestContext = createAppRootMockRenderer(); + + apiMocks = responseActionsHttpMocks(appTestContext.coreStart.http); + + renderProps = { + action: new EndpointActionGenerator('seed').generateActionDetails< + ResponseActionExecuteOutputContent, + ResponseActionsExecuteParameters + >({ command: 'execute' }), + 'data-test-subj': 'test', + }; + + render = () => { + renderResult = appTestContext.render(); + return renderResult; + }; + }); + + it('should show execute output and execute errors', async () => { + render(); + expect(renderResult.getByTestId('test')).toBeTruthy(); + }); + + it('should show loading when details are fetching', async () => { + (renderProps.action as ActionDetails).outputs = {}; + + const deferred = getDeferred(); + + apiMocks.responseProvider.actionDetails.mockDelay.mockReturnValue(deferred.promise); + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + + render(); + expect(renderResult.getByTestId('test-loading')).toBeTruthy(); + + // Release the `action details` api + deferred.resolve(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + expect(renderResult.queryByTestId('test-loading')).toBeNull(); + expect(renderResult.getByTestId('test')).toBeTruthy(); + }); + + it('should show nothing when no output in action details', () => { + (renderProps.action as ActionDetails).outputs = {}; + render(); + expect(renderResult.queryByTestId('test')).toBeNull(); + }); + + it('should handle API error', async () => { + (renderProps.action as ActionDetails).outputs = {}; + const error = { message: 'server error', response: { status: 500 } } as IHttpFetchError; + + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + apiMocks.responseProvider.actionDetails.mockImplementation(() => { + throw error; + }); + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('test-apiError')).toHaveTextContent('server error'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx index 3624f577716dd..959c6ad0ce5e0 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx @@ -4,27 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useEffect, useMemo, useState } from 'react'; import { EuiAccordion, - EuiFlexGroup, EuiFlexItem, + EuiSkeletonText, EuiSpacer, EuiText, useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ResponseActionFileDownloadLink } from '../response_action_file_download_link'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; +import { useGetActionDetails } from '../../hooks/response_actions/use_get_action_details'; import type { ActionDetails, MaybeImmutable, ResponseActionExecuteOutputContent, } from '../../../../common/endpoint/types'; - -const EXECUTE_FILE_LINK_TITLE = i18n.translate( - 'xpack.securitySolution.responseActionExecuteDownloadLink.downloadButtonLabel', - { defaultMessage: 'Click here to download full output' } -); +import { FormattedError } from '../formatted_error'; const ACCORDION_BUTTON_TEXT = Object.freeze({ output: { @@ -57,14 +54,15 @@ const ACCORDION_BUTTON_TEXT = Object.freeze({ }, }); interface ExecuteActionOutputProps { - content: string; + content?: string; initialIsOpen?: boolean; - isTruncated: boolean; + isTruncated?: boolean; + textSize?: 's' | 'xs'; type: 'error' | 'output'; } const ExecutionActionOutputAccordion = memo( - ({ content, initialIsOpen = false, isTruncated, type }) => { + ({ content, initialIsOpen = false, isTruncated = false, textSize, type }) => { const id = useGeneratedHtmlId({ prefix: 'executeActionOutputAccordions', suffix: type, @@ -77,7 +75,7 @@ const ExecutionActionOutputAccordion = memo( paddingSize="s" > ( ); ExecutionActionOutputAccordion.displayName = 'ExecutionActionOutputAccordion'; -interface ExecuteActionHostResponseOutputProps { +export interface ExecuteActionHostResponseOutputProps { action: MaybeImmutable; agentId?: string; 'data-test-subj'?: string; @@ -99,42 +97,75 @@ interface ExecuteActionHostResponseOutputProps { } export const ExecuteActionHostResponseOutput = memo( - ({ action, agentId = action.agents[0], 'data-test-subj': dataTestSubj, textSize }) => { + ({ action, agentId = action.agents[0], 'data-test-subj': dataTestSubj, textSize = 'xs' }) => { + const isMounted = useIsMounted(); const outputContent = useMemo( () => action.outputs && action.outputs[agentId] && (action.outputs[agentId].content as ResponseActionExecuteOutputContent), - [agentId, action] + [action.outputs, agentId] ); - return ( - - - - + const { + error, + data: actionDetails, + isFetching, + isFetched, + } = useGetActionDetails(action.id, { + enabled: !outputContent, + }); + + const [executeOutputContent, setExecuteOutputContent] = useState< + undefined | ResponseActionExecuteOutputContent + >(outputContent); - {outputContent && ( - - - - - - )} - + useEffect(() => { + if ( + isMounted() && + isFetched && + actionDetails && + actionDetails.data && + actionDetails.data.outputs && + actionDetails.data.outputs[agentId] + ) { + setExecuteOutputContent( + actionDetails.data.outputs[agentId].content as ResponseActionExecuteOutputContent + ); + } + + return () => { + setExecuteOutputContent(undefined); + }; + }, [actionDetails, agentId, isFetched, isMounted]); + + if (isFetching && !executeOutputContent) { + return ( + + ); + } + + if (error) { + return ; + } + + return ( + + + + + ); } ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx index fe766f2015516..e5ea73de39cf4 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx @@ -75,6 +75,8 @@ export const ExecuteActionResult = memo< ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx index b65e13003c263..13ad9da09637a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx @@ -7,6 +7,7 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useSendGetFileRequest } from '../../../hooks/response_actions/use_send_get_file_request'; import type { ResponseActionGetFileRequestBody } from '../../../../../common/endpoint/schema/actions'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; @@ -18,6 +19,7 @@ export const GetFileActionResult = memo< path: string[]; }> >(({ command, setStore, store, status, setStatus, ResultComponent }) => { + const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; const actionCreator = useSendGetFileRequest(); const actionRequestBody = useMemo(() => { @@ -59,7 +61,10 @@ export const GetFileActionResult = memo< { defaultMessage: 'File retrieved from the host.' } )} > - + ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx new file mode 100644 index 0000000000000..5be50866a9130 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx @@ -0,0 +1,237 @@ +/* + * 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, useMemo } from 'react'; +import { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiDescriptionList } from '@elastic/eui'; +import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; +import { OUTPUT_MESSAGES } from '../translations'; +import { getUiCommand } from './hooks'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { ResponseActionFileDownloadLink } from '../../response_action_file_download_link'; +import { ExecuteActionHostResponseOutput } from '../../endpoint_execute_action'; +import { getEmptyValue } from '../../../../common/components/empty_value'; + +import { type ActionDetails, type MaybeImmutable } from '../../../../../common/endpoint/types'; +const EXECUTE_FILE_LINK_TITLE = i18n.translate( + 'xpack.securitySolution.responseActionExecuteDownloadLink.downloadButtonLabel', + { defaultMessage: 'Click here to download full output' } +); +const emptyValue = getEmptyValue(); + +const customDescriptionListCss = css` + &.euiDescriptionList { + > .euiDescriptionList__title { + color: ${(props) => props.theme.eui.euiColorDarkShade}; + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + } + + > .euiDescriptionList__title, + > .euiDescriptionList__description { + font-weight: ${(props) => props.theme.eui.euiFontWeightRegular}; + margin-top: ${(props) => props.theme.eui.euiSizeS}; + } + } +`; +const topSpacingCss = css` + ${(props) => `${props.theme.eui.euiSize} 0`} +`; +const dashedBorderCss = css` + ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; +`; +const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` + ${customDescriptionListCss} + dd { + margin: ${topSpacingCss}; + padding: ${topSpacingCss}; + border-top: ${dashedBorderCss}; + border-bottom: ${dashedBorderCss}; + } +`; + +const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ + compressed: true, + type: 'column', +})` + ${customDescriptionListCss} +`; + +const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ + transparentBackground: true, + paddingSize: 'none', +})` + code { + color: ${(props) => props.theme.eui.euiColorDarkShade} !important; + } +`; + +const StyledEuiFlexGroup = euiStyled(EuiFlexGroup).attrs({ + direction: 'column', + className: 'eui-yScrollWithShadows', + gutterSize: 's', +})` + max-height: 270px; + overflow-y: auto; +`; + +const OutputContent = memo<{ action: MaybeImmutable; 'data-test-subj'?: string }>( + ({ action, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { canWriteFileOperations, canWriteExecuteOperations } = + useUserPrivileges().endpointPrivileges; + + const { command, isCompleted, isExpired, wasSuccessful } = action; + + if (isExpired) { + return <>{OUTPUT_MESSAGES.hasExpired(command)}; + } + + if (!isCompleted) { + return <>{OUTPUT_MESSAGES.isPending(command)}; + } + + if (!wasSuccessful) { + return <>{OUTPUT_MESSAGES.hasFailed(command)}; + } + + if (command === 'get-file') { + return ( + <> + {OUTPUT_MESSAGES.wasSuccessful(command)} + + + ); + } + + if (command === 'execute') { + return ( + + {action.agents.map((agentId) => ( +
    + {OUTPUT_MESSAGES.wasSuccessful(command)} + + + + +
    + ))} +
    + ); + } + + return <>{OUTPUT_MESSAGES.wasSuccessful(command)}; + } +); + +OutputContent.displayName = 'OutputContent'; + +export const ActionsLogExpandedTray = memo<{ + action: MaybeImmutable; + 'data-test-subj'?: string; +}>(({ action, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { startedAt, completedAt, command: _command, comment, parameters } = action; + + const parametersList = useMemo( + () => + parameters + ? Object.entries(parameters).map(([key, value]) => { + return `${key}:${value}`; + }) + : undefined, + [parameters] + ); + + const command = getUiCommand(_command); + + const dataList = useMemo( + () => + [ + { + title: OUTPUT_MESSAGES.expandSection.placedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.startedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.completedAt, + description: `${completedAt ?? emptyValue}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.input, + description: `${command}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.parameters, + description: parametersList ? parametersList : emptyValue, + }, + { + title: OUTPUT_MESSAGES.expandSection.comment, + description: comment ? comment : emptyValue, + }, + ].map(({ title, description }) => { + return { + title: {title}, + description: {description}, + }; + }), + [command, comment, completedAt, parametersList, startedAt] + ); + + const outputList = useMemo( + () => [ + { + title: ( + {`${OUTPUT_MESSAGES.expandSection.output}:`} + ), + description: ( + // codeblock for output + + + + ), + }, + ], + [action, dataTestSubj, getTestId] + ); + + return ( + <> + + + + + + + + + + ); +}); + +ActionsLogExpandedTray.displayName = 'ActionsLogExpandedTray'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx index af0988040f80e..fef40433c6848 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx @@ -41,6 +41,7 @@ export const ActionLogDateRangePicker = memo( onRefresh, onRefreshChange, onTimeChange, + 'data-test-subj': dataTestSubj, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; @@ -48,9 +49,10 @@ export const ActionLogDateRangePicker = memo( onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; + 'data-test-subj'?: string; }) => { const { startDate: startDateFromUrl, endDate: endDateFromUrl } = useActionHistoryUrlParams(); - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const kibana = useKibana(); const { uiSettings } = kibana.services; const [commonlyUsedRanges] = useState(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx index 8482e275f7811..8b77b8152a177 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx @@ -20,12 +20,14 @@ export const ActionsLogFilter = memo( filterName, isFlyout, onChangeFilterOptions, + 'data-test-subj': dataTestSubj, }: { filterName: FilterName; isFlyout: boolean; onChangeFilterOptions: (selectedOptions: string[]) => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); // popover states and handlers const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -177,6 +179,7 @@ export const ActionsLogFilter = memo( numActiveFilters={numActiveFilters} numFilters={numFilters} onButtonClick={onPopoverButtonClick} + data-test-subj={dataTestSubj} > void; @@ -30,8 +31,9 @@ export const ActionsLogFilterPopover = memo( numActiveFilters: number; numFilters: number; onButtonClick: () => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const filterGroupPopoverId = useGeneratedHtmlId({ prefix: 'filterGroupPopover', diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx index 9fb8cce0f291a..9215fd5fa3cee 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx @@ -33,6 +33,7 @@ export const ActionsLogFilters = memo( onRefreshChange, onTimeChange, showHostsFilter, + 'data-test-subj': dataTestSubj, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; @@ -46,8 +47,9 @@ export const ActionsLogFilters = memo( onTimeChange: ({ start, end }: DurationRange) => void; onClick: ReturnType['refetch']; showHostsFilter: boolean; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const filters = useMemo(() => { return ( <> @@ -56,21 +58,25 @@ export const ActionsLogFilters = memo( filterName={'hosts'} isFlyout={isFlyout} onChangeFilterOptions={onChangeHostsFilter} + data-test-subj={dataTestSubj} /> )} ); }, [ + dataTestSubj, isFlyout, onChangeCommandsFilter, onChangeHostsFilter, @@ -83,7 +89,11 @@ export const ActionsLogFilters = memo( return ( - + {filters} @@ -96,6 +106,7 @@ export const ActionsLogFilters = memo( onRefresh={onRefresh} onRefreshChange={onRefreshChange} onTimeChange={onTimeChange} + data-test-subj={dataTestSubj} /> diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx new file mode 100644 index 0000000000000..09767754dc7fb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx @@ -0,0 +1,387 @@ +/* + * 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, useCallback, useMemo, useState } from 'react'; + +import type { CriteriaWithPagination } from '@elastic/eui'; +import { + EuiI18nNumber, + EuiAvatar, + EuiBasicTable, + EuiButtonIcon, + EuiFacetButton, + EuiHorizontalRule, + RIGHT_ALIGNMENT, + EuiScreenReaderOnly, + EuiText, + EuiToolTip, + type HorizontalAlignment, +} from '@elastic/eui'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { ActionListApiResponse } from '../../../../../common/endpoint/types'; +import type { EndpointActionListRequestQuery } from '../../../../../common/endpoint/schema/actions'; +import { FormattedDate } from '../../../../common/components/formatted_date'; +import { TABLE_COLUMN_NAMES, UX_MESSAGES, ARIA_LABELS } from '../translations'; +import { getActionStatus, getUiCommand } from './hooks'; +import { getEmptyValue } from '../../../../common/components/empty_value'; +import { StatusBadge } from './status_badge'; +import { ActionsLogExpandedTray } from './action_log_expanded_tray'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; +import { useActionHistoryUrlParams } from './use_action_history_url_params'; +import { useUrlPagination } from '../../../hooks/use_url_pagination'; + +const emptyValue = getEmptyValue(); + +// Truncated usernames +const StyledFacetButton = euiStyled(EuiFacetButton)` + .euiText { + margin-top: 0.38rem; + overflow-y: visible !important; + } +`; + +interface ActionsLogTableProps { + error?: string; + 'data-test-subj'?: string; + items: ActionListApiResponse['data']; + isFlyout: boolean; + loading: boolean; + onChange: ({ + page: _page, + }: CriteriaWithPagination) => void; + queryParams: EndpointActionListRequestQuery; + showHostNames: boolean; + totalItemCount: number; +} +interface ExpandedRowMapType { + [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; +} + +export const ActionsLogTable = memo( + ({ + 'data-test-subj': dataTestSubj, + error, + items, + isFlyout, + loading, + onChange, + queryParams, + showHostNames, + totalItemCount, + }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { pagination: paginationFromUrlParams } = useUrlPagination(); + const { withOutputs, setUrlWithOutputs } = useActionHistoryUrlParams(); + + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState( + withOutputs + ? withOutputs.reduce((idToRowMap, actionId) => { + if (actionId.length) { + idToRowMap[actionId] = ( + item.id === actionId)[0]} + data-test-subj={dataTestSubj} + /> + ); + } + return idToRowMap; + }, {}) + : {} + ); + + const toggleDetails = useCallback( + (item: ActionListApiResponse['data'][number]) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[item.id]) { + // close tray + delete itemIdToExpandedRowMapValues[item.id]; + } else { + // expanded tray contents + itemIdToExpandedRowMapValues[item.id] = ( + + ); + } + const expandedActionIds = Object.keys(itemIdToExpandedRowMapValues); + if (!isFlyout) { + // set and show `withOutputs` URL param on history page + setUrlWithOutputs(expandedActionIds.join()); + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }, + [dataTestSubj, isFlyout, itemIdToExpandedRowMap, setUrlWithOutputs] + ); + // memoized callback for toggleDetails + const onClickCallback = useCallback( + (actionListDataItem: ActionListApiResponse['data'][number]) => () => + toggleDetails(actionListDataItem), + [toggleDetails] + ); + + const responseActionListColumns = useMemo(() => { + const columns = [ + { + field: 'startedAt', + name: TABLE_COLUMN_NAMES.time, + width: !showHostNames ? '21%' : '15%', + truncateText: true, + render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { + return ( + + ); + }, + }, + { + field: 'command', + name: TABLE_COLUMN_NAMES.command, + width: !showHostNames ? '21%' : '10%', + truncateText: true, + render: (_command: ActionListApiResponse['data'][number]['command']) => { + const command = getUiCommand(_command); + return ( + + + {command} + + + ); + }, + }, + { + field: 'createdBy', + name: TABLE_COLUMN_NAMES.user, + width: !showHostNames ? '21%' : '14%', + truncateText: true, + render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { + return ( + + } + > + + + {userId} + + + + ); + }, + }, + // conditional hostnames column + { + field: 'hosts', + name: TABLE_COLUMN_NAMES.hosts, + width: '20%', + truncateText: true, + render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { + const hosts = _hosts && Object.values(_hosts); + // join hostnames if the action is for multiple agents + // and skip empty strings for names if any + const _hostnames = hosts + .reduce((acc, host) => { + if (host.name.trim()) { + acc.push(host.name); + } + return acc; + }, []) + .join(', '); + + let hostnames = _hostnames; + if (!_hostnames) { + if (hosts.length > 1) { + // when action was for a single agent and no host name + hostnames = UX_MESSAGES.unenrolled.hosts; + } else if (hosts.length === 1) { + // when action was for a multiple agents + // and none of them have a host name + hostnames = UX_MESSAGES.unenrolled.host; + } + } + return ( + + + {hostnames} + + + ); + }, + }, + { + field: 'comment', + name: TABLE_COLUMN_NAMES.comments, + width: !showHostNames ? '21%' : '30%', + truncateText: true, + render: (comment: ActionListApiResponse['data'][number]['comment']) => { + return ( + + + {comment ?? emptyValue} + + + ); + }, + }, + { + field: 'status', + name: TABLE_COLUMN_NAMES.status, + width: !showHostNames ? '15%' : '10%', + render: (_status: ActionListApiResponse['data'][number]['status']) => { + const status = getActionStatus(_status); + + return ( + + + + ); + }, + }, + { + field: '', + align: RIGHT_ALIGNMENT as HorizontalAlignment, + width: '40px', + isExpander: true, + name: ( + + {UX_MESSAGES.screenReaderExpand} + + ), + render: (actionListDataItem: ActionListApiResponse['data'][number]) => { + return ( + + ); + }, + }, + ]; + // filter out the `hosts` column + // if showHostNames is FALSE + if (!showHostNames) { + return columns.filter((column) => column.field !== 'hosts'); + } + return columns; + }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); + + // table pagination + const tablePagination = useMemo(() => { + return { + pageIndex: isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1, + pageSize: isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize, + totalItemCount, + pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], + }; + }, [ + isFlyout, + paginationFromUrlParams.page, + paginationFromUrlParams.pageSize, + queryParams.page, + queryParams.pageSize, + totalItemCount, + ]); + + // compute record ranges + const pagedResultsCount = useMemo(() => { + const page = queryParams.page ?? 1; + const perPage = queryParams?.pageSize ?? 10; + + const totalPages = Math.ceil(totalItemCount / perPage); + const fromCount = perPage * page - perPage + 1; + const toCount = + page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; + return { fromCount, toCount }; + }, [queryParams.page, queryParams.pageSize, totalItemCount]); + + // create range label to display + const recordRangeLabel = useMemo( + () => ( + + + + {'-'} + + + ), + total: , + recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, + }} + /> + + ), + [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] + ); + + return ( + <> + {recordRangeLabel} + + + + ); + } +); + +ActionsLogTable.displayName = 'ActionsLogTable'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx index 60b5f5368dafc..e6a4c4c242576 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx @@ -23,7 +23,8 @@ describe('Users filter', () => { let history: AppContextTestRender['history']; let mockedContext: AppContextTestRender; - const testPrefix = 'response-actions-list-users-filter'; + const testPrefix = 'test'; + const filterPrefix = 'users-filter'; let onChangeUsersFilter: jest.Mock; beforeEach(async () => { @@ -32,7 +33,11 @@ describe('Users filter', () => { ({ history } = mockedContext); render = (props?: React.ComponentProps) => (renderResult = mockedContext.render( - + )); reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/response_actions`); @@ -42,7 +47,7 @@ describe('Users filter', () => { it('should show a search input for users', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); expect(searchInput).toBeTruthy(); expect(searchInput.getAttribute('placeholder')).toEqual('Filter by username'); }); @@ -50,7 +55,7 @@ describe('Users filter', () => { it('should search on given search string on enter', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, 'usernameX'); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX']); @@ -59,7 +64,7 @@ describe('Users filter', () => { it('should search comma separated strings as multiple users', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, 'usernameX,usernameY,usernameZ'); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX', 'usernameY', 'usernameZ']); @@ -68,7 +73,7 @@ describe('Users filter', () => { it('should ignore white spaces in a given username when updating the API params', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, ' usernameX '); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX']); @@ -77,7 +82,7 @@ describe('Users filter', () => { it('should ignore white spaces in comma separated usernames when updating the API params', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, ' , usernameX ,usernameY , '); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX', 'usernameY']); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx index d9de7986c5e80..68cbfbffeb261 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx @@ -15,11 +15,13 @@ export const ActionsLogUsersFilter = memo( ({ isFlyout, onChangeUsersFilter, + 'data-test-subj': dataTestSubj, }: { isFlyout: boolean; onChangeUsersFilter: (selectedUserIds: string[]) => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const { users: usersFromUrlParams, setUrlUsersFilters } = useActionHistoryUrlParams(); const [searchValue, setSearchValue] = useState(''); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx index fd8138a3f08e5..046811160c57d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx @@ -6,14 +6,19 @@ */ import React, { memo } from 'react'; import { EuiBadge, type EuiBadgeProps } from '@elastic/eui'; -import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export const StatusBadge = memo( - ({ color, status }: { color: EuiBadgeProps['color']; status: string }) => { - const getTestId = useTestIdGenerator('response-actions-list'); - + ({ + color, + status, + 'data-test-subj': dataTestSubj, + }: { + color: EuiBadgeProps['color']; + 'data-test-subj'?: string; + status: string; + }) => { return ( - + {status} ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts index a0e1623055f26..b483ed8a132cc 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts @@ -22,11 +22,13 @@ interface UrlParamsActionsLogFilters { startDate: string; endDate: string; users: string; + withOutputs: string; } interface ActionsLogFiltersFromUrlParams { commands?: ConsoleResponseActionCommands[]; hosts?: string[]; + withOutputs?: string[]; statuses?: ResponseActionStatus[]; startDate?: string; endDate?: string; @@ -35,12 +37,13 @@ interface ActionsLogFiltersFromUrlParams { setUrlHostsFilters: (agentIds: UrlParamsActionsLogFilters['hosts']) => void; setUrlStatusesFilters: (statuses: UrlParamsActionsLogFilters['statuses']) => void; setUrlUsersFilters: (users: UrlParamsActionsLogFilters['users']) => void; + setUrlWithOutputs: (outputs: UrlParamsActionsLogFilters['withOutputs']) => void; users?: string[]; } type FiltersFromUrl = Pick< ActionsLogFiltersFromUrlParams, - 'commands' | 'hosts' | 'statuses' | 'users' | 'startDate' | 'endDate' + 'commands' | 'hosts' | 'withOutputs' | 'statuses' | 'users' | 'startDate' | 'endDate' >; export const actionsLogFiltersFromUrlParams = ( @@ -53,6 +56,7 @@ export const actionsLogFiltersFromUrlParams = ( startDate: 'now-24h/h', endDate: 'now', users: [], + withOutputs: [], }; const urlCommands = urlParams.commands @@ -72,6 +76,10 @@ export const actionsLogFiltersFromUrlParams = ( const urlHosts = urlParams.hosts ? String(urlParams.hosts).split(',').sort() : []; + const urlWithOutputs = urlParams.withOutputs + ? String(urlParams.withOutputs).split(',').sort() + : []; + const urlStatuses = urlParams.statuses ? (String(urlParams.statuses).split(',') as ResponseActionStatus[]).reduce< ResponseActionStatus[] @@ -91,6 +99,7 @@ export const actionsLogFiltersFromUrlParams = ( actionsLogFilters.startDate = urlParams.startDate ? String(urlParams.startDate) : undefined; actionsLogFilters.endDate = urlParams.endDate ? String(urlParams.endDate) : undefined; actionsLogFilters.users = urlUsers.length ? urlUsers : undefined; + actionsLogFilters.withOutputs = urlWithOutputs.length ? urlWithOutputs : undefined; return actionsLogFilters; }; @@ -133,6 +142,19 @@ export const useActionHistoryUrlParams = (): ActionsLogFiltersFromUrlParams => { [history, location, toUrlParams, urlParams] ); + const setUrlWithOutputs = useCallback( + (actionIds: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + withOutputs: actionIds.length ? actionIds : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + const setUrlStatusesFilters = useCallback( (statuses: string) => { history.push({ @@ -187,6 +209,7 @@ export const useActionHistoryUrlParams = (): ActionsLogFiltersFromUrlParams => { setUrlActionsFilters, setUrlDateRangeFilters, setUrlHostsFilters, + setUrlWithOutputs, setUrlStatusesFilters, setUrlUsersFilters, }; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index ae8d652aeaeb9..39ef4f40211b8 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -16,6 +16,7 @@ import { } from '../../../common/mock/endpoint'; import { ResponseActionsLog } from './response_actions_log'; import type { + ActionDetailsApiResponse, ActionFileInfoApiResponse, ActionListApiResponse, } from '../../../../common/endpoint/types'; @@ -138,6 +139,20 @@ jest.mock('../../hooks/response_actions/use_get_file_info', () => { }; }); +let mockUseGetActionDetails: { + isFetching?: boolean; + isFetched?: boolean; + error?: Partial | null; + data?: ActionDetailsApiResponse; +}; +jest.mock('../../hooks/response_actions/use_get_action_details', () => { + const original = jest.requireActual('../../hooks/response_actions/use_get_action_details'); + return { + ...original, + useGetActionDetails: () => mockUseGetActionDetails, + }; +}); + const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; const getBaseMockedActionList = () => ({ @@ -147,7 +162,7 @@ const getBaseMockedActionList = () => ({ refetch: jest.fn(), }); describe('Response actions history', () => { - const testPrefix = 'response-actions-list'; + const testPrefix = 'test'; let render: ( props?: React.ComponentProps @@ -161,7 +176,9 @@ describe('Response actions history', () => { mockedContext = createAppRootMockRenderer(); ({ history } = mockedContext); render = (props?: React.ComponentProps) => - (renderResult = mockedContext.render()); + (renderResult = mockedContext.render( + + )); reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/response_actions`); }); @@ -246,7 +263,7 @@ describe('Response actions history', () => { const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 13 response actions' ); @@ -256,9 +273,7 @@ describe('Response actions history', () => { render({ agentIds: 'agent-a' }); expect( - Array.from( - renderResult.getByTestId(`${testPrefix}-table-view`).querySelectorAll('thead th') - ) + Array.from(renderResult.getByTestId(`${testPrefix}`).querySelectorAll('thead th')) .slice(0, 6) .map((col) => col.textContent) ).toEqual(['Time', 'Command', 'User', 'Comments', 'Status', 'Expand rows']); @@ -268,9 +283,7 @@ describe('Response actions history', () => { render({ showHostNames: true }); expect( - Array.from( - renderResult.getByTestId(`${testPrefix}-table-view`).querySelectorAll('thead th') - ) + Array.from(renderResult.getByTestId(`${testPrefix}`).querySelectorAll('thead th')) .slice(0, 7) .map((col) => col.textContent) ).toEqual(['Time', 'Command', 'User', 'Hosts', 'Comments', 'Status', 'Expand rows']); @@ -347,7 +360,7 @@ describe('Response actions history', () => { render(); const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 13 response actions' ); @@ -368,7 +381,7 @@ describe('Response actions history', () => { render(); const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 33 response actions' ); @@ -438,91 +451,6 @@ describe('Response actions history', () => { ); }); - it('should contain download link in expanded row for `get-file` action WITH file operation permission', async () => { - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - mockUseGetFileInfo = { - isFetching: false, - error: null, - data: apiMocks.responseProvider.fileInfo(), - }; - - render(); - - const { getByTestId } = renderResult; - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - - await waitFor(() => { - expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); - }); - - const downloadLink = getByTestId(`${testPrefix}-getFileDownloadLink`); - expect(downloadLink).toBeTruthy(); - expect(downloadLink.textContent).toEqual( - 'Click here to download(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' - ); - }); - - it('should show file unavailable for download for `get-file` action WITH file operation permission when file is deleted', async () => { - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - const fileInfo = apiMocks.responseProvider.fileInfo(); - fileInfo.data.status = 'DELETED'; - - apiMocks.responseProvider.fileInfo.mockReturnValue(fileInfo); - - mockUseGetFileInfo = { - isFetching: false, - error: null, - data: apiMocks.responseProvider.fileInfo(), - }; - - render(); - - const { getByTestId } = renderResult; - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - - await waitFor(() => { - expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); - }); - - const unavailableText = getByTestId( - `${testPrefix}-getFileDownloadLink-fileNoLongerAvailable` - ); - expect(unavailableText).toBeTruthy(); - }); - - it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { - useUserPrivilegesMock.mockReturnValue({ - endpointPrivileges: getEndpointAuthzInitialStateMock({ - canWriteFileOperations: false, - }), - }); - - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - render(); - const { getByTestId, queryByTestId } = renderResult; - - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - const output = getByTestId(`${testPrefix}-details-tray-output`); - expect(output).toBeTruthy(); - expect(output.textContent).toEqual('get-file completed successfully'); - expect(queryByTestId(`${testPrefix}-getFileDownloadLink`)).toBeNull(); - }); - it('should refresh data when autoRefresh is toggled on', async () => { mockUseGetEndpointActionList = getBaseMockedActionList(); render(); @@ -570,6 +498,217 @@ describe('Response actions history', () => { userEvent.click(getByTestId('superDatePickerCommonlyUsed_Last_15 minutes')); expect(startDatePopoverButton).toHaveTextContent('Last 15 minutes'); }); + + describe('`get-file` action', () => { + it('should contain download link in expanded row for `get-file` action WITH file operation permission', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const downloadLink = getByTestId(`${testPrefix}-getFileDownloadLink`); + expect(downloadLink).toBeTruthy(); + expect(downloadLink.textContent).toEqual( + 'Click here to download(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + }); + + it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteFileOperations: false, + }), + }); + + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), + }; + + render(); + const { getByTestId, queryByTestId } = renderResult; + + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + const output = getByTestId(`${testPrefix}-details-tray-output`); + expect(output).toBeTruthy(); + expect(output.textContent).toEqual('get-file completed successfully'); + expect(queryByTestId(`${testPrefix}-getFileDownloadLink`)).toBeNull(); + }); + }); + + describe('`execute` action', () => { + it('should contain full output download link in expanded row for `execute` action WITH execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: true, + canWriteFileOperations: false, + }), + }); + const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: actionDetails, + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + mockUseGetActionDetails = { + isFetching: false, + isFetched: true, + error: null, + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }), + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }).data, + outputs: { + [actionDetails.data[0].agents[0]]: { + content: {}, + type: 'json', + }, + }, + }, + }, + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const downloadExecuteLink = getByTestId(`${testPrefix}-getExecuteLink`); + expect(downloadExecuteLink).toBeTruthy(); + expect(downloadExecuteLink.textContent).toEqual( + 'Click here to download full output(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + }); + + it('should contain execute output and error for `execute` action WITH execute operation privilege', async () => { + const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: actionDetails, + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + mockUseGetActionDetails = { + isFetching: false, + isFetched: true, + error: null, + data: { + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }).data, + outputs: { + [actionDetails.data[0].agents[0]]: { + content: {}, + type: 'json', + }, + }, + }, + }, + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const executeAccordions = getByTestId(`${testPrefix}-executeResponseOutput`); + expect(executeAccordions).toBeTruthy(); + expect(executeAccordions).toHaveTextContent('Execution outputExecution error'); + }); + + it('should contain execute output for `execute` action WITHOUT execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['execute'] }), + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + const executeAccordions = getByTestId(`${testPrefix}-executeResponseOutput`); + expect(executeAccordions).toBeTruthy(); + }); + + it('should not contain full output download link in expanded row for `execute` action WITHOUT execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['execute'] }), + }; + + render(); + const { getByTestId, queryByTestId } = renderResult; + + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + expect(queryByTestId(`${testPrefix}-getExecuteLink`)).toBeNull(); + + const output = getByTestId(`${testPrefix}-details-tray-output`); + expect(output).toBeTruthy(); + expect(output.textContent).toContain('execute completed successfully'); + }); + }); }); describe('Action status ', () => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index fc046ff574685..39601035250e9 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBasicTable, EuiEmptyPrompt, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexItem } from '@elastic/eui'; import type { CriteriaWithPagination } from '@elastic/eui'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; @@ -27,247 +27,244 @@ import { useActionHistoryUrlParams } from './components/use_action_history_url_p import { useUrlPagination } from '../../hooks/use_url_pagination'; import { ManagementPageLoader } from '../management_page_loader'; import { ActionsLogEmptyState } from './components/actions_log_empty_state'; -import { useResponseActionsLogTable } from './use_response_actions_log_table'; +import { ActionsLogTable } from './components/actions_log_table'; export const ResponseActionsLog = memo< Pick & { showHostNames?: boolean; isFlyout?: boolean; setIsDataInResponse?: (isData: boolean) => void; + 'data-test-subj'?: string; } ->(({ agentIds, showHostNames = false, isFlyout = true, setIsDataInResponse }) => { - const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = - useUrlPagination(); - const { - commands: commandsFromUrl, - hosts: agentIdsFromUrl, - statuses: statusesFromUrl, - startDate: startDateFromUrl, - endDate: endDateFromUrl, - users: usersFromUrl, - } = useActionHistoryUrlParams(); +>( + ({ + agentIds, + showHostNames = false, + isFlyout = true, + setIsDataInResponse, + 'data-test-subj': dataTestSubj = 'response-actions-list', + }) => { + const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = + useUrlPagination(); + const { + commands: commandsFromUrl, + hosts: agentIdsFromUrl, + statuses: statusesFromUrl, + startDate: startDateFromUrl, + endDate: endDateFromUrl, + users: usersFromUrl, + } = useActionHistoryUrlParams(); - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); - // Used to decide if display global loader or not (only the fist time tha page loads) - const [isFirstAttempt, setIsFirstAttempt] = useState(true); + // Used to decide if display global loader or not (only the fist time tha page loads) + const [isFirstAttempt, setIsFirstAttempt] = useState(true); - const [queryParams, setQueryParams] = useState({ - page: isFlyout ? 1 : paginationFromUrlParams.page, - pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, - agentIds: isFlyout ? agentIds : agentIdsFromUrl?.length ? agentIdsFromUrl : agentIds, - commands: [], - statuses: [], - userIds: [], - }); - - // update query state from URL params - useEffect(() => { - if (!isFlyout) { - setQueryParams((prevState) => ({ - ...prevState, - commands: commandsFromUrl?.length - ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) - : prevState.commands, - hosts: agentIdsFromUrl?.length ? agentIdsFromUrl : prevState.agentIds, - statuses: statusesFromUrl?.length - ? (statusesFromUrl as ResponseActionStatus[]) - : prevState.statuses, - userIds: usersFromUrl?.length ? usersFromUrl : prevState.userIds, - })); - } - }, [commandsFromUrl, agentIdsFromUrl, isFlyout, statusesFromUrl, setQueryParams, usersFromUrl]); + const [queryParams, setQueryParams] = useState({ + page: isFlyout ? 1 : paginationFromUrlParams.page, + pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, + agentIds: isFlyout ? agentIds : agentIdsFromUrl?.length ? agentIdsFromUrl : agentIds, + commands: [], + statuses: [], + userIds: [], + }); - // date range picker state and handlers - const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); + // update query state from URL params + useEffect(() => { + if (!isFlyout) { + setQueryParams((prevState) => ({ + ...prevState, + commands: commandsFromUrl?.length + ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) + : prevState.commands, + hosts: agentIdsFromUrl?.length ? agentIdsFromUrl : prevState.agentIds, + statuses: statusesFromUrl?.length + ? (statusesFromUrl as ResponseActionStatus[]) + : prevState.statuses, + userIds: usersFromUrl?.length ? usersFromUrl : prevState.userIds, + })); + } + }, [commandsFromUrl, agentIdsFromUrl, isFlyout, statusesFromUrl, setQueryParams, usersFromUrl]); - // initial fetch of list data - const { - error, - data: actionList, - isFetching, - isFetched, - refetch: reFetchEndpointActionList, - } = useGetEndpointActionList( - { - ...queryParams, - startDate: isFlyout ? dateRangePickerState.startDate : startDateFromUrl, - endDate: isFlyout ? dateRangePickerState.endDate : endDateFromUrl, - }, - { retry: false } - ); + // date range picker state and handlers + const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); - // total actions - const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); + // initial fetch of list data + const { + error, + data: actionList, + isFetching, + isFetched, + refetch: reFetchEndpointActionList, + } = useGetEndpointActionList( + { + ...queryParams, + startDate: isFlyout ? dateRangePickerState.startDate : startDateFromUrl, + endDate: isFlyout ? dateRangePickerState.endDate : endDateFromUrl, + }, + { retry: false } + ); - // table columns and expanded row state - const { itemIdToExpandedRowMap, recordRangeLabel, responseActionListColumns, tablePagination } = - useResponseActionsLogTable({ - showHostNames, - pageIndex: isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1, - pageSize: isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize, - queryParams, - totalItemCount, - }); + // total actions + const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); + // table items + const tableItems = useMemo(() => actionList?.data ?? [], [actionList?.data]); - // Hide page header when there is no actions index calling the setIsDataInResponse with false value. - // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track - // if the API request was done for the first time. - useEffect(() => { - if ( - !isFetching && - error?.body?.statusCode === 404 && - error?.body?.message === 'index_not_found_exception' - ) { - if (setIsDataInResponse) { - setIsDataInResponse(false); - } - } else if (!isFetching && actionList) { - setIsFirstAttempt(false); - if (setIsDataInResponse) { - setIsDataInResponse(true); + // Hide page header when there is no actions index calling the setIsDataInResponse with false value. + // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track + // if the API request was done for the first time. + useEffect(() => { + if ( + !isFetching && + error?.body?.statusCode === 404 && + error?.body?.message === 'index_not_found_exception' + ) { + if (setIsDataInResponse) { + setIsDataInResponse(false); + } + } else if (!isFetching && actionList) { + setIsFirstAttempt(false); + if (setIsDataInResponse) { + setIsDataInResponse(true); + } } - } - }, [actionList, error, isFetching, setIsDataInResponse]); + }, [actionList, error, isFetching, setIsDataInResponse]); - // handle auto refresh data - const onRefresh = useCallback(() => { - if (dateRangePickerState.autoRefreshOptions.enabled) { - reFetchEndpointActionList(); - } - }, [dateRangePickerState.autoRefreshOptions.enabled, reFetchEndpointActionList]); + // handle auto refresh data + const onRefresh = useCallback(() => { + if (dateRangePickerState.autoRefreshOptions.enabled) { + reFetchEndpointActionList(); + } + }, [dateRangePickerState.autoRefreshOptions.enabled, reFetchEndpointActionList]); - // handle on change actions filter - const onChangeCommandsFilter = useCallback( - (selectedCommands: string[]) => { - setQueryParams((prevState) => ({ - ...prevState, - commands: selectedCommands as ResponseActionsApiCommandNames[], - })); - }, - [setQueryParams] - ); + // handle on change actions filter + const onChangeCommandsFilter = useCallback( + (selectedCommands: string[]) => { + setQueryParams((prevState) => ({ + ...prevState, + commands: selectedCommands as ResponseActionsApiCommandNames[], + })); + }, + [setQueryParams] + ); - // handle on change actions filter - const onChangeStatusesFilter = useCallback( - (selectedStatuses: string[]) => { - setQueryParams((prevState) => ({ - ...prevState, - statuses: selectedStatuses as ResponseActionStatus[], - })); - }, - [setQueryParams] - ); + // handle on change actions filter + const onChangeStatusesFilter = useCallback( + (selectedStatuses: string[]) => { + setQueryParams((prevState) => ({ + ...prevState, + statuses: selectedStatuses as ResponseActionStatus[], + })); + }, + [setQueryParams] + ); - // handle on change hosts filter - const onChangeHostsFilter = useCallback( - (selectedAgentIds: string[]) => { - setQueryParams((prevState) => ({ ...prevState, agentIds: selectedAgentIds })); - }, - [setQueryParams] - ); + // handle on change hosts filter + const onChangeHostsFilter = useCallback( + (selectedAgentIds: string[]) => { + setQueryParams((prevState) => ({ ...prevState, agentIds: selectedAgentIds })); + }, + [setQueryParams] + ); - // handle on change users filter - const onChangeUsersFilter = useCallback( - (selectedUserIds: string[]) => { - setQueryParams((prevState) => ({ ...prevState, userIds: selectedUserIds })); - }, - [setQueryParams] - ); + // handle on change users filter + const onChangeUsersFilter = useCallback( + (selectedUserIds: string[]) => { + setQueryParams((prevState) => ({ ...prevState, userIds: selectedUserIds })); + }, + [setQueryParams] + ); - // handle onChange - const handleTableOnChange = useCallback( - ({ page: _page }: CriteriaWithPagination) => { - // table paging is 0 based - const { index, size } = _page; - // adjust the page to conform to - // 1-based API page - const pagingArgs = { - page: index + 1, - pageSize: size, - }; + // handle onChange + const handleTableOnChange = useCallback( + ({ page: _page }: CriteriaWithPagination) => { + // table paging is 0 based + const { index, size } = _page; + // adjust the page to conform to + // 1-based API page + const pagingArgs = { + page: index + 1, + pageSize: size, + }; - setQueryParams((prevState) => ({ - ...prevState, - ...pagingArgs, - })); - if (!isFlyout) { - setPaginationOnUrlParams({ + setQueryParams((prevState) => ({ + ...prevState, ...pagingArgs, - }); - } - reFetchEndpointActionList(); - }, - [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] - ); + })); + if (!isFlyout) { + setPaginationOnUrlParams({ + ...pagingArgs, + }); + } + reFetchEndpointActionList(); + }, + [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] + ); - if (error?.body?.statusCode === 404 && error?.body?.message === 'index_not_found_exception') { - return ; - } else if (isFetching && isFirstAttempt) { - return ; - } - return ( - <> - - {isFetched && !totalItemCount ? ( - - - - - - } - body={ -

    - -

    - } - data-test-subj="responseActions-empty" - /> -
    -
    - ) : ( - <> - {recordRangeLabel} - - ; + } else if (isFetching && isFirstAttempt) { + return ; + } + return ( + <> + + {isFetched && !totalItemCount ? ( + + + + + + } + body={ +

    + +

    + } + data-test-subj="responseActions-empty" + /> +
    +
    + ) : ( + - - )} - - ); -}); + )} + + ); + } +); ResponseActionsLog.displayName = 'ResponseActionsLog'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index 363da92a025bf..d185500612deb 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -180,3 +180,15 @@ export const FILTER_NAMES = Object.freeze({ defaultMessage: 'Filter by username', }), }); + +export const ARIA_LABELS = Object.freeze({ + collapse: i18n.translate( + 'xpack.securitySolution.responseActionsList.list.expandButton.collapse', + { + defaultMessage: 'Collapse', + } + ), + expand: i18n.translate('xpack.securitySolution.responseActionsList.list.expandButton.expand', { + defaultMessage: 'Expand', + }), +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx deleted file mode 100644 index 4ecd860a4876e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx +++ /dev/null @@ -1,471 +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, { useCallback, useMemo, useState } from 'react'; - -import { - EuiI18nNumber, - EuiAvatar, - EuiButtonIcon, - EuiCodeBlock, - EuiDescriptionList, - EuiFacetButton, - EuiFlexGroup, - EuiFlexItem, - RIGHT_ALIGNMENT, - EuiScreenReaderOnly, - EuiText, - EuiToolTip, - type HorizontalAlignment, -} from '@elastic/eui'; -import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import type { ActionListApiResponse } from '../../../../common/endpoint/types'; -import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; -import { FormattedDate } from '../../../common/components/formatted_date'; -import { OUTPUT_MESSAGES, TABLE_COLUMN_NAMES, UX_MESSAGES } from './translations'; -import { getActionStatus, getUiCommand } from './components/hooks'; -import { getEmptyValue } from '../../../common/components/empty_value'; -import { StatusBadge } from './components/status_badge'; -import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; -import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants'; -import { ResponseActionFileDownloadLink } from '../response_action_file_download_link'; - -const emptyValue = getEmptyValue(); - -// Truncated usernames -const StyledFacetButton = euiStyled(EuiFacetButton)` - .euiText { - margin-top: 0.38rem; - overflow-y: visible !important; - } -`; - -const customDescriptionListCss = css` - &.euiDescriptionList { - > .euiDescriptionList__title { - color: ${(props) => props.theme.eui.euiColorDarkShade}; - font-size: ${(props) => props.theme.eui.euiFontSizeXS}; - } - - > .euiDescriptionList__title, - > .euiDescriptionList__description { - font-weight: ${(props) => props.theme.eui.euiFontWeightRegular}; - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } - } -`; - -const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ - compressed: true, - type: 'column', -})` - ${customDescriptionListCss} -`; - -// output section styles -const topSpacingCss = css` - ${(props) => `${props.theme.eui.euiSize} 0`} -`; -const dashedBorderCss = css` - ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; -`; -const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` - ${customDescriptionListCss} - dd { - margin: ${topSpacingCss}; - padding: ${topSpacingCss}; - border-top: ${dashedBorderCss}; - border-bottom: ${dashedBorderCss}; - } -`; - -// code block styles -const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ - transparentBackground: true, - paddingSize: 'none', -})` - code { - color: ${(props) => props.theme.eui.euiColorDarkShade} !important; - } -`; - -export const useResponseActionsLogTable = ({ - pageIndex, - pageSize, - queryParams, - showHostNames, - totalItemCount, -}: { - pageIndex: number; - pageSize: number; - queryParams: EndpointActionListRequestQuery; - showHostNames: boolean; - totalItemCount: number; -}) => { - const getTestId = useTestIdGenerator('response-actions-list'); - - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ - [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; - }>({}); - - // expanded tray contents - const toggleDetails = useCallback( - (item: ActionListApiResponse['data'][number]) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item.id]) { - delete itemIdToExpandedRowMapValues[item.id]; - } else { - const { - startedAt, - completedAt, - isCompleted, - wasSuccessful, - isExpired, - command: _command, - comment, - parameters, - } = item; - - const parametersList = parameters - ? Object.entries(parameters).map(([key, value]) => { - return `${key}:${value}`; - }) - : undefined; - - const command = getUiCommand(_command); - const isGetFileCommand = command === 'get-file'; - const dataList = [ - { - title: OUTPUT_MESSAGES.expandSection.placedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.startedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.completedAt, - description: `${completedAt ?? emptyValue}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.input, - description: `${command}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.parameters, - description: parametersList ? parametersList : emptyValue, - }, - { - title: OUTPUT_MESSAGES.expandSection.comment, - description: comment ? comment : emptyValue, - }, - ].map(({ title, description }) => { - return { - title: {title}, - description: {description}, - }; - }); - - const getOutputContent = () => { - if (isExpired) { - return OUTPUT_MESSAGES.hasExpired(command); - } - - if (!isCompleted) { - return OUTPUT_MESSAGES.isPending(command); - } - - if (!wasSuccessful) { - return OUTPUT_MESSAGES.hasFailed(command); - } - - if (isGetFileCommand) { - return ( - <> - {OUTPUT_MESSAGES.wasSuccessful(command)} - - - ); - } - - return OUTPUT_MESSAGES.wasSuccessful(command); - }; - - const outputList = [ - { - title: ( - {`${OUTPUT_MESSAGES.expandSection.output}:`} - ), - description: ( - // codeblock for output - - {getOutputContent()} - - ), - }, - ]; - - itemIdToExpandedRowMapValues[item.id] = ( - <> - - - - - - - - - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }, - [getTestId, itemIdToExpandedRowMap] - ); - // memoized callback for toggleDetails - const onClickCallback = useCallback( - (actionListDataItem: ActionListApiResponse['data'][number]) => () => - toggleDetails(actionListDataItem), - [toggleDetails] - ); - - const responseActionListColumns = useMemo(() => { - const columns = [ - { - field: 'startedAt', - name: TABLE_COLUMN_NAMES.time, - width: !showHostNames ? '21%' : '15%', - truncateText: true, - render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { - return ( - - ); - }, - }, - { - field: 'command', - name: TABLE_COLUMN_NAMES.command, - width: !showHostNames ? '21%' : '10%', - truncateText: true, - render: (_command: ActionListApiResponse['data'][number]['command']) => { - const command = getUiCommand(_command); - return ( - - - {command} - - - ); - }, - }, - { - field: 'createdBy', - name: TABLE_COLUMN_NAMES.user, - width: !showHostNames ? '21%' : '14%', - truncateText: true, - render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { - return ( - - } - > - - - {userId} - - - - ); - }, - }, - // conditional hostnames column - { - field: 'hosts', - name: TABLE_COLUMN_NAMES.hosts, - width: '20%', - truncateText: true, - render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { - const hosts = _hosts && Object.values(_hosts); - // join hostnames if the action is for multiple agents - // and skip empty strings for names if any - const _hostnames = hosts - .reduce((acc, host) => { - if (host.name.trim()) { - acc.push(host.name); - } - return acc; - }, []) - .join(', '); - - let hostnames = _hostnames; - if (!_hostnames) { - if (hosts.length > 1) { - // when action was for a single agent and no host name - hostnames = UX_MESSAGES.unenrolled.hosts; - } else if (hosts.length === 1) { - // when action was for a multiple agents - // and none of them have a host name - hostnames = UX_MESSAGES.unenrolled.host; - } - } - return ( - - - {hostnames} - - - ); - }, - }, - { - field: 'comment', - name: TABLE_COLUMN_NAMES.comments, - width: !showHostNames ? '21%' : '30%', - truncateText: true, - render: (comment: ActionListApiResponse['data'][number]['comment']) => { - return ( - - - {comment ?? emptyValue} - - - ); - }, - }, - { - field: 'status', - name: TABLE_COLUMN_NAMES.status, - width: !showHostNames ? '15%' : '10%', - render: (_status: ActionListApiResponse['data'][number]['status']) => { - const status = getActionStatus(_status); - - return ( - - - - ); - }, - }, - { - field: '', - align: RIGHT_ALIGNMENT as HorizontalAlignment, - width: '40px', - isExpander: true, - name: ( - - {UX_MESSAGES.screenReaderExpand} - - ), - render: (actionListDataItem: ActionListApiResponse['data'][number]) => { - return ( - - ); - }, - }, - ]; - // filter out the `hosts` column - // if showHostNames is FALSE - if (!showHostNames) { - return columns.filter((column) => column.field !== 'hosts'); - } - return columns; - }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); - - // table pagination - const tablePagination = useMemo(() => { - return { - pageIndex, - pageSize, - totalItemCount, - pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], - }; - }, [pageIndex, pageSize, totalItemCount]); - - // compute record ranges - const pagedResultsCount = useMemo(() => { - const page = queryParams.page ?? 1; - const perPage = queryParams?.pageSize ?? 10; - - const totalPages = Math.ceil(totalItemCount / perPage); - const fromCount = perPage * page - perPage + 1; - const toCount = - page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; - return { fromCount, toCount }; - }, [queryParams.page, queryParams.pageSize, totalItemCount]); - - // create range label to display - const recordRangeLabel = useMemo( - () => ( - - - - {'-'} - - - ), - total: , - recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, - }} - /> - - ), - [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] - ); - - return { itemIdToExpandedRowMap, responseActionListColumns, recordRangeLabel, tablePagination }; -}; diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx index b212b884f77b8..e8a7d387ca0ba 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx @@ -20,18 +20,11 @@ import { type ResponseActionFileDownloadLinkProps, } from './response_action_file_download_link'; import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; -import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges'; import { getDeferred } from '../../mocks/utils'; import { waitFor } from '@testing-library/react'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -jest.mock('../../../common/components/user_privileges'); - describe('When using the `ResponseActionFileDownloadLink` component', () => { - const useUserPrivilegesMock = _useUserPrivileges as jest.Mock< - ReturnType - >; - let render: () => ReturnType; let renderResult: ReturnType; let renderProps: ResponseActionFileDownloadLinkProps; @@ -48,6 +41,7 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { ResponseActionGetFileParameters >({ command: 'get-file' }), 'data-test-subj': 'test', + canAccessFileDownloadLink: true, }; render = () => { @@ -152,18 +146,8 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { }); }); - it('should show nothing if user does not have authz', () => { - const privileges = useUserPrivilegesMock(); - - useUserPrivilegesMock.mockImplementationOnce(() => { - return { - ...privileges, - endpointPrivileges: { - ...privileges.endpointPrivileges, - canWriteFileOperations: false, - }, - }; - }); + it('should show nothing if user does not have permission', () => { + renderProps.canAccessFileDownloadLink = false; render(); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx index f17ac1a4b205f..c795bfc9bc191 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useMemo, type CSSProperties } from 'react'; -import { EuiButtonEmpty, EuiLoadingContent, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiSkeletonText, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -14,7 +14,6 @@ import { getFileDownloadId } from '../../../../common/endpoint/service/response_ import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; import { FormattedError } from '../formatted_error'; import { useGetFileInfo } from '../../hooks/response_actions/use_get_file_info'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import type { MaybeImmutable } from '../../../../common/endpoint/types'; import type { ActionDetails } from '../../../../common/endpoint/types/actions'; @@ -45,6 +44,7 @@ export interface ResponseActionFileDownloadLinkProps { /** If left undefined, the first agent that the action was sent to will be used */ agentId?: string; buttonTitle?: string; + canAccessFileDownloadLink: boolean; 'data-test-subj'?: string; textSize?: 's' | 'xs'; } @@ -60,12 +60,12 @@ export const ResponseActionFileDownloadLink = memo { const action = _action as ActionDetails; // cast to remove `Immutable` const getTestId = useTestIdGenerator(dataTestSubj); - const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; const shouldFetchFileInfo: boolean = useMemo(() => { return action.isCompleted && action.wasSuccessful; @@ -83,15 +83,15 @@ export const ResponseActionFileDownloadLink = memo; + return ; } // Check if file is no longer available diff --git a/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx b/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx new file mode 100644 index 0000000000000..1eb5a656b3628 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx @@ -0,0 +1,43 @@ +/* + * 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 { SVGProps } from 'react'; +import React from 'react'; +export const IconCloudDefend: React.FC> = ({ ...props }) => ( + + + + + + + + + + + + + +); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 1db32eae3d5f2..57f2c53a7be19 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -49,6 +49,7 @@ import { manageCategories as cloudSecurityPostureCategories, manageLinks as cloudSecurityPostureLinks, } from '../cloud_security_posture/links'; +import { manageLinks as cloudDefendLinks } from '../cloud_defend/links'; import { IconActionHistory } from './icons/action_history'; import { IconBlocklist } from './icons/blocklist'; import { IconEndpoints } from './icons/endpoints'; @@ -226,6 +227,7 @@ export const links: LinkItem = { hideTimeline: true, }, cloudSecurityPostureLinks, + cloudDefendLinks, ], }; diff --git a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx index 24076272f37ae..a6e98483a03a6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx @@ -13,6 +13,8 @@ import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { useUserPrivileges } from '../../../common/components/user_privileges'; import { endpointPageHttpMock } from '../endpoint_hosts/mocks'; +import { ExperimentalFeaturesService } from '../../../common/experimental_features_service'; +import { allowedExperimentalValues } from '../../../../common/experimental_features'; jest.mock('../../../common/components/user_privileges'); @@ -22,6 +24,12 @@ describe('when in the Administration tab', () => { let render: () => ReturnType; const mockedContext = createAppRootMockRenderer(); + beforeAll(() => { + ExperimentalFeaturesService.init({ + experimentalFeatures: { ...allowedExperimentalValues }, + }); + }); + beforeEach(() => { endpointPageHttpMock(mockedContext.coreStart.http); render = () => mockedContext.render(); @@ -33,6 +41,13 @@ describe('when in the Administration tab', () => { }); describe('when the user has no permissions', () => { + // remove this beforeAll hook when feature flag is removed + beforeAll(() => { + ExperimentalFeaturesService.init({ + experimentalFeatures: { ...allowedExperimentalValues, endpointRbacEnabled: true }, + }); + }); + it('should display `no permission` if no `canAccessEndpointManagement`', async () => { useUserPrivilegesMock.mockReturnValue({ endpointPrivileges: { loading: false, canAccessEndpointManagement: false }, @@ -96,8 +111,14 @@ describe('when in the Administration tab', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/145204 - describe.skip('when the user has permissions', () => { + describe('when the user has permissions', () => { + // remove this beforeAll hook when feature flag is removed + beforeAll(() => { + ExperimentalFeaturesService.init({ + experimentalFeatures: { ...allowedExperimentalValues, endpointRbacEnabled: true }, + }); + }); + it('should display the Management view if user has privileges', async () => { useUserPrivilegesMock.mockReturnValue({ endpointPrivileges: { loading: false, canReadEndpointList: true, canAccessFleet: true }, diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index abc9c41101e25..78680086d45c0 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -395,6 +395,7 @@ export class Plugin implements IPlugin; management: ReturnType; landingPages: ReturnType; + cloudDefend: ReturnType; cloudSecurityPosture: ReturnType; threatIntelligence: ReturnType; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts index 38f03c987ea34..788e7f3ae7713 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts @@ -63,6 +63,24 @@ export const supportedSchemas: SupportedSchema[] = [ name: 'process.name', }, }, + { + name: 'sysmonViaFilebeat', + constraints: [ + { + field: 'agent.type', + value: 'filebeat', + }, + { + field: 'event.dataset', + value: 'windows.sysmon_operational', + }, + ], + schema: { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + name: 'process.name', + }, + }, ]; export function getFieldAsString(doc: unknown, field: string): string | undefined { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index 869ae911ad890..bd0642702fc11 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -86,6 +86,7 @@ export class EventsQuery extends BaseResolverQuery { return { body: this.query(filters), index: this.indexPatterns, + allow_partial_search_results: true, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts index a44be24ef2fca..c0b50798a822c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts @@ -61,7 +61,7 @@ describe('Pagination', () => { const builder = PaginationBuilder.createBuilder(100); expect(builder.buildQueryFields('a', 'desc').sort).toStrictEqual([ { '@timestamp': 'desc' }, - { a: 'asc' }, + { a: { order: 'asc', unmapped_type: 'long' } }, ]); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts index 96086f1312f85..295d930ff24f5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts @@ -31,7 +31,7 @@ export type SortFields = [ { '@timestamp': string; }, - { [x: string]: string } + { [x: string]: { order: string; unmapped_type?: string } } ]; /** @@ -177,7 +177,10 @@ export class PaginationBuilder { tiebreaker: string, timeSort: TimeSortDirection = 'asc' ): PaginationFields { - const sort: SortFields = [{ '@timestamp': timeSort }, { [tiebreaker]: 'asc' }]; + const sort: SortFields = [ + { '@timestamp': timeSort }, + { [tiebreaker]: { order: 'asc', unmapped_type: 'long' } }, + ]; let searchAfter: SearchAfterFields | undefined; if (this.timestamp && this.eventID) { searchAfter = [this.timestamp, this.eventID]; diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index 197b9fc926cc7..5f838f76d3bf5 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -7,10 +7,14 @@ import { i18n } from '@kbn/i18n'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; -import { createUICapabilities } from '@kbn/cases-plugin/common'; +import { + createUICapabilities as createCasesUICapabilities, + getApiTags as getCasesApiTags, +} from '@kbn/cases-plugin/common'; import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; import { APP_ID, CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants'; @@ -18,7 +22,8 @@ import { savedObjectTypes } from './saved_objects'; import type { ConfigType } from './config'; export const getCasesKibanaFeature = (): KibanaFeatureConfig => { - const casesCapabilities = createUICapabilities(); + const casesCapabilities = createCasesUICapabilities(); + const casesApiTags = getCasesApiTags(APP_ID); return { id: CASES_FEATURE_ID, @@ -32,7 +37,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { cases: [APP_ID], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.all, app: [CASES_FEATURE_ID, 'kibana'], catalogue: [APP_ID], cases: { @@ -42,13 +47,13 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { push: [APP_ID], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.read, app: [CASES_FEATURE_ID, 'kibana'], catalogue: [APP_ID], cases: { @@ -56,7 +61,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.read, }, @@ -71,7 +76,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { groupType: 'independent', privileges: [ { - api: [], + api: casesApiTags.delete, id: 'cases_delete', name: i18n.translate( 'xpack.securitySolution.featureRegistry.deleteSubFeatureDetails', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts index af35f7881bd00..dcf9d006c4315 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { PackageListItem, PackagePolicy } from '@kbn/fleet-plugin/common'; import { capitalize, flatten } from 'lodash'; -import type { PackagePolicy, ArchivePackage } from '@kbn/fleet-plugin/common'; import type { InstalledIntegration, InstalledIntegrationArray, @@ -17,8 +17,8 @@ import type { } from '../../../../../../common/detection_engine/fleet_integrations'; export interface IInstalledIntegrationSet { + addPackage(fleetPackage: PackageListItem): void; addPackagePolicy(policy: PackagePolicy): void; - addRegistryPackage(registryPackage: ArchivePackage): void; getPackages(): InstalledPackageArray; getIntegrations(): InstalledIntegrationArray; @@ -33,10 +33,57 @@ interface PackageInfo extends InstalledPackageBasicInfo { export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { const packageMap: PackageMap = new Map([]); + const addPackage = (fleetPackage: PackageListItem): void => { + if (fleetPackage.type !== 'integration') { + return; + } + if (fleetPackage.status !== 'installed') { + return; + } + + const packageKey = `${fleetPackage.name}`; + const existingPackageInfo = packageMap.get(packageKey); + + if (existingPackageInfo != null) { + return; + } + + // Actual `installed_version` is buried in SO, root `version` is latest package version available + const installedPackageVersion = fleetPackage.savedObject.attributes.install_version; + + // Policy templates correspond to package's integrations. + const packagePolicyTemplates = fleetPackage.policy_templates ?? []; + + const packageInfo: PackageInfo = { + package_name: fleetPackage.name, + package_title: fleetPackage.title, + package_version: installedPackageVersion, + + integrations: new Map( + packagePolicyTemplates.map((pt) => { + const integrationTitle: string = + packagePolicyTemplates.length === 1 && pt.name === fleetPackage.name + ? fleetPackage.title + : pt.title; + + const integrationInfo: InstalledIntegrationBasicInfo = { + integration_name: pt.name, + integration_title: integrationTitle, + is_enabled: false, // There might not be an integration policy, so default false and later update in addPackagePolicy() + }; + + return [integrationInfo.integration_name, integrationInfo]; + }) + ), + }; + + packageMap.set(packageKey, packageInfo); + }; + const addPackagePolicy = (policy: PackagePolicy): void => { const packageInfo = getPackageInfoFromPolicy(policy); const integrationsInfo = getIntegrationsInfoFromPolicy(policy, packageInfo); - const packageKey = `${packageInfo.package_name}:${packageInfo.package_version}`; + const packageKey = `${packageInfo.package_name}`; const existingPackageInfo = packageMap.get(packageKey); if (existingPackageInfo == null) { @@ -56,21 +103,6 @@ export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { } }; - const addRegistryPackage = (registryPackage: ArchivePackage): void => { - const policyTemplates = registryPackage.policy_templates ?? []; - const packageKey = `${registryPackage.name}:${registryPackage.version}`; - const existingPackageInfo = packageMap.get(packageKey); - - if (existingPackageInfo != null) { - for (const integration of existingPackageInfo.integrations.values()) { - const policyTemplate = policyTemplates.find((t) => t.name === integration.integration_name); - if (policyTemplate != null) { - integration.integration_title = policyTemplate.title; - } - } - } - }; - const getPackages = (): InstalledPackageArray => { const packages = Array.from(packageMap.values()); return packages.map((packageInfo): InstalledPackage => { @@ -106,8 +138,8 @@ export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { }; return { + addPackage, addPackagePolicy, - addRegistryPackage, getPackages, getIntegrations, }; @@ -125,15 +157,30 @@ const getIntegrationsInfoFromPolicy = ( policy: PackagePolicy, packageInfo: InstalledPackageBasicInfo ): InstalledIntegrationBasicInfo[] => { - return policy.inputs.map((input) => { + // Construct integration info from the available policy_templates + const integrationInfos = policy.inputs.map((input) => { const integrationName = normalizeString(input.policy_template ?? input.type); // e.g. 'cloudtrail' const integrationTitle = `${packageInfo.package_title} ${capitalize(integrationName)}`; // e.g. 'AWS Cloudtrail' return { integration_name: integrationName, - integration_title: integrationTitle, // title gets re-initialized later in addRegistryPackage() + integration_title: integrationTitle, is_enabled: input.enabled, }; }); + + // Base package may not have policy template, so pull directly from `policy.package` if so + return [ + ...integrationInfos, + ...(policy.package + ? [ + { + integration_name: policy.package.name, + integration_title: policy.package.title, + is_enabled: true, // Always true if `policy.package` exists since this corresponds to the base package + }, + ] + : []), + ]; }; const normalizeString = (raw: string | null | undefined): string => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index 7b904c282e1e4..559abe391f5a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -7,7 +7,6 @@ import type { Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { initPromisePool } from '../../../../../utils/promise_pool'; import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; @@ -15,8 +14,6 @@ import type { GetInstalledIntegrationsResponse } from '../../../../../../common/ import { GET_INSTALLED_INTEGRATIONS_URL } from '../../../../../../common/detection_engine/fleet_integrations'; import { createInstalledIntegrationSet } from './installed_integration_set'; -const MAX_CONCURRENT_REQUESTS_TO_PACKAGE_REGISTRY = 5; - /** * Returns an array of installed Fleet integrations and their packages. */ @@ -40,48 +37,18 @@ export const getInstalledIntegrationsRoute = ( const fleet = ctx.securitySolution.getInternalFleetServices(); const set = createInstalledIntegrationSet(); - const packagePolicies = await fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}); + // Pulls all packages into memory just like the main fleet landing page + // No pagination support currently, so cannot batch this call + const allThePackages = await fleet.packages.getPackages(); + allThePackages.forEach((fleetPackage) => { + set.addPackage(fleetPackage); + }); + const packagePolicies = await fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}); packagePolicies.items.forEach((policy) => { set.addPackagePolicy(policy); }); - const registryPackages = await initPromisePool({ - concurrency: MAX_CONCURRENT_REQUESTS_TO_PACKAGE_REGISTRY, - items: set.getPackages(), - executor: async (packageInfo) => { - const registryPackage = await fleet.packages.getPackage( - packageInfo.package_name, - packageInfo.package_version - ); - return registryPackage; - }, - }); - - if (registryPackages.errors.length > 0) { - const errors = registryPackages.errors.map(({ error, item }) => { - return { - error, - packageId: `${item.package_name}@${item.package_version}`, - }; - }); - - const packages = errors.map((e) => e.packageId).join(', '); - logger.error( - `Unable to retrieve installed integrations. Error fetching packages from registry: ${packages}.` - ); - - errors.forEach(({ error, packageId }) => { - const logMessage = `Error fetching package info from registry for ${packageId}`; - const logReason = error instanceof Error ? error.message : String(error); - logger.debug(`${logMessage}. ${logReason}`); - }); - } - - registryPackages.results.forEach(({ result }) => { - set.addRegistryPackage(result.packageInfo); - }); - const installedIntegrations = set.getIntegrations(); const body: GetInstalledIntegrationsResponse = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 7d822733b4da5..a200afdd34199 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -21,7 +21,9 @@ export interface OutputError { statusCode: number; } export interface BulkError { + // Id can be single id or stringified ids. id?: string; + // rule_id can be single rule_id or stringified rules ids. rule_id?: string; error: { status_code: number; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts index 2f81d1284eb53..54574c85f7036 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts @@ -174,6 +174,7 @@ describe('importRuleActionConnectors', () => { '1 connector is missing. Connector id missing is: cabc78e0-9031-11ed-b076-53cc4d57aaf1', status_code: 404, }, + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', rule_id: 'rule-1', }, ], @@ -220,6 +221,7 @@ describe('importRuleActionConnectors', () => { status_code: 404, }, rule_id: 'rule-1', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1,cabc78e0-9031-11ed-b076-53cc4d57aaf2', }, ], warnings: [], @@ -270,6 +272,7 @@ describe('importRuleActionConnectors', () => { status_code: 404, }, rule_id: 'rule-1,rule-2', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1,cabc78e0-9031-11ed-b076-53cc4d57aaf2', }, ], warnings: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts index caea8ad664f01..4ab122f6da94e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts @@ -43,6 +43,7 @@ export const handleActionsHaveNoConnectors = ( : 'connector is missing. Connector id missing is:'; errors.push( createBulkErrorObject({ + id: actionsIds.join(), statusCode: 404, message: `${actionsIds.length} ${errorMessage} ${actionsIds.join(', ')}`, ruleId: ruleIds, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts index 70819f6896861..99b6834523ef5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts @@ -61,6 +61,7 @@ export const buildThreatEnrichment = ({ const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams, eventsCount: signals.length, + termsQueryAllowed: false, }); const enrichment = threatEnrichmentFactory({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts index 8fea8412be38a..17ce22aa40bee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts @@ -86,6 +86,7 @@ export const createEventSignal = async ({ threatSearchParams, eventsCount: currentEventList.length, signalValueMap: getSignalValueMap({ eventList: currentEventList, threatMatchedFields }), + termsQueryAllowed: true, }); const ids = Array.from(signalsQueryMap.keys()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts index 38a6947beebcb..4a48db4816b48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts @@ -53,6 +53,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(getThreatListMock).toHaveBeenCalledTimes(1); @@ -65,6 +66,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual(new Map()); @@ -98,6 +100,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual( @@ -153,6 +156,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap.get('source-1')).toHaveLength(MAX_NUMBER_OF_SIGNAL_MATCHES); @@ -168,6 +172,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual(new Map()); @@ -201,6 +206,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); expect(signalsQueryMap).toEqual(new Map()); @@ -234,6 +240,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); const queries = [ @@ -283,6 +290,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); const queries = [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts index 0deb3beeee2e8..7d0f49b548f37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts @@ -21,20 +21,34 @@ import { MAX_NUMBER_OF_SIGNAL_MATCHES } from './enrich_signal_threat_matches'; export type SignalsQueryMap = Map; -interface GetSignalsMatchesFromThreatIndexOptions { +interface GetSignalsQueryMapFromThreatIndexOptionsTerms { threatSearchParams: Omit; eventsCount: number; - signalValueMap?: SignalValuesMap; + signalValueMap: SignalValuesMap; + termsQueryAllowed: true; +} + +interface GetSignalsQueryMapFromThreatIndexOptionsMatch { + threatSearchParams: Omit; + eventsCount: number; + termsQueryAllowed: false; } /** * fetches threats and creates signals map from results, that matches signal is with list of threat queries */ -export const getSignalsQueryMapFromThreatIndex = async ({ - threatSearchParams, - eventsCount, - signalValueMap, -}: GetSignalsMatchesFromThreatIndexOptions): Promise => { +/** + * fetches threats and creates signals map from results, that matches signal is with list of threat queries + * @param options.termsQueryAllowed - if terms query allowed to be executed, then signalValueMap should be provided + * @param options.signalValueMap - map of signal values from terms query results + */ +export async function getSignalsQueryMapFromThreatIndex( + options: + | GetSignalsQueryMapFromThreatIndexOptionsTerms + | GetSignalsQueryMapFromThreatIndexOptionsMatch +): Promise { + const { threatSearchParams, eventsCount, termsQueryAllowed } = options; + let threatList: Awaited> | undefined; const signalsQueryMap = new Map(); // number of threat matches per signal is limited by MAX_NUMBER_OF_SIGNAL_MATCHES. Once it hits this number, threats stop to be processed for a signal @@ -50,9 +64,6 @@ export const getSignalsQueryMapFromThreatIndex = async ({ decodedQuery: ThreatMatchNamedQuery | ThreatTermNamedQuery; }) => { const signalMatch = signalsQueryMap.get(signalId); - if (!signalMatch) { - signalsQueryMap.set(signalId, []); - } const threatQuery = { id: threatHit._id, @@ -74,15 +85,9 @@ export const getSignalsQueryMapFromThreatIndex = async ({ } }; - while ( - maxThreatsReachedMap.size < eventsCount && - (threatList ? threatList?.hits.hits.length > 0 : true) - ) { - threatList = await getThreatList({ - ...threatSearchParams, - searchAfter: threatList?.hits.hits[threatList.hits.hits.length - 1].sort || undefined, - }); + threatList = await getThreatList({ ...threatSearchParams, searchAfter: undefined }); + while (maxThreatsReachedMap.size < eventsCount && threatList?.hits.hits.length > 0) { threatList.hits.hits.forEach((threatHit) => { const matchedQueries = threatHit?.matched_queries || []; @@ -90,13 +95,13 @@ export const getSignalsQueryMapFromThreatIndex = async ({ const decodedQuery = decodeThreatMatchNamedQuery(matchedQuery); const signalId = decodedQuery.id; - if (decodedQuery.queryType === ThreatMatchQueryType.term) { + if (decodedQuery.queryType === ThreatMatchQueryType.term && termsQueryAllowed) { const threatValue = get(threatHit?._source, decodedQuery.value); const values = Array.isArray(threatValue) ? threatValue : [threatValue]; values.forEach((value) => { - if (value && signalValueMap) { - const ids = signalValueMap[decodedQuery.field][value?.toString()]; + if (value && options.signalValueMap) { + const ids = options.signalValueMap[decodedQuery.field][value?.toString()]; ids?.forEach((id: string) => { addSignalValueToMap({ @@ -120,7 +125,12 @@ export const getSignalsQueryMapFromThreatIndex = async ({ } }); }); + + threatList = await getThreatList({ + ...threatSearchParams, + searchAfter: threatList.hits.hits[threatList.hits.hits.length - 1].sort, + }); } return signalsQueryMap; -}; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts index 5aac1418cf45d..1a5733cbd7760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts @@ -1327,6 +1327,35 @@ describe('merge_all_fields_with_source', () => { }); }); + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + process: { + command_line: 'string longer than 10 characters', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + process: { + command_line: ['string longer than 10 characters'], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + + expect(merged).toEqual(_source); + }); + test('multi-field values mixed with regular values will not be merged accidentally"', () => { const _source: SignalSourceHit['_source'] = {}; const fields: SignalSourceHit['fields'] = { @@ -1393,6 +1422,32 @@ describe('merge_all_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + 'process.command_line': 'string longer than 10 characters', + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + 'process.command_line': ['string longer than 10 characters'], + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts index b9193f952fd18..ab9cc86fa1049 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts @@ -15,8 +15,8 @@ import { isNestedObject } from '../utils/is_nested_object'; import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; import { isPrimitive } from '../utils/is_primitive'; import { isArrayOfPrimitives } from '../utils/is_array_of_primitives'; -import { arrayInPathExists } from '../utils/array_in_path_exists'; import { isTypeObject } from '../utils/is_type_object'; +import { isPathValid } from '../utils/is_path_valid'; /** * Merges all of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information @@ -107,7 +107,7 @@ const hasEarlyReturnConditions = ({ const valueInMergedDocument = get(fieldsKey, merged); return ( fieldsValue.length === 0 || - (valueInMergedDocument === undefined && arrayInPathExists(fieldsKey, merged)) || + (valueInMergedDocument === undefined && !isPathValid(fieldsKey, merged)) || (isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) && !isNestedObject(fieldsValue) && !isTypeObject(fieldsValue)) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts index 911df7400ec63..7e997ceb0ee65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts @@ -1283,6 +1283,38 @@ describe('merge_missing_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + process: { + command_line: 'string longer than 10 characters', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + process: { + command_line: ['string longer than 10 characters'], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); describe('flattened keys for the _source', () => { @@ -1331,6 +1363,36 @@ describe('merge_missing_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + 'process.command_line': 'string longer than 10 characters', + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + 'process.command_line': ['string longer than 10 characters'], + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts index 3efe1a7925d9b..e4bf563f4f055 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts @@ -12,8 +12,8 @@ import { filterFieldEntries } from '../utils/filter_field_entries'; import type { FieldsType, MergeStrategyFunction } from '../types'; import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; import { isTypeObject } from '../utils/is_type_object'; -import { arrayInPathExists } from '../utils/array_in_path_exists'; import { isNestedObject } from '../utils/is_nested_object'; +import { isPathValid } from '../utils/is_path_valid'; /** * Merges only missing sections of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information @@ -79,7 +79,7 @@ const hasEarlyReturnConditions = ({ return ( fieldsValue.length === 0 || valueInMergedDocument !== undefined || - arrayInPathExists(fieldsKey, merged) || + !isPathValid(fieldsKey, merged) || isNestedObject(fieldsValue) || isTypeObject(fieldsValue) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts new file mode 100644 index 0000000000000..e899142bb7352 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { isPathValid } from './is_path_valid'; + +describe('isPathValid', () => { + test('not valid when empty string and empty object', () => { + expect(isPathValid('', {})).toEqual(false); + }); + + test('valid when a path and empty object', () => { + expect(isPathValid('a.b.c', {})).toEqual(true); + }); + + test('not valid when a path and an array exists', () => { + expect(isPathValid('a', { a: [] })).toEqual(false); + }); + + test('not valid when a path and primitive value exists', () => { + expect(isPathValid('a', { a: 'test' })).toEqual(false); + expect(isPathValid('a', { a: 1 })).toEqual(false); + expect(isPathValid('a', { a: true })).toEqual(false); + }); + + test('valid when a path and object value exists', () => { + expect(isPathValid('a', { a: {} })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: [] })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: 'test' })).toEqual(false); + expect(isPathValid('a.b', { a: 1 })).toEqual(false); + expect(isPathValid('a.b', { a: true })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: {} })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 2', () => { + expect(isPathValid('a.b.c', { a: { b: [] } })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 2', () => { + expect(isPathValid('a.b', { a: { b: 'test' } })).toEqual(false); + expect(isPathValid('a.b', { a: { b: 1 } })).toEqual(false); + expect(isPathValid('a.b', { a: { b: true } })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 2', () => { + expect(isPathValid('a.b', { a: { b: {} } })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: [] } } })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: 'test' } } })).toEqual(false); + expect(isPathValid('a.b.c', { a: { b: { c: 1 } } })).toEqual(false); + expect(isPathValid('a.b.c', { a: { b: { c: true } } })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: {} } } })).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts new file mode 100644 index 0000000000000..c5038531baa2a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts @@ -0,0 +1,30 @@ +/* + * 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 { get, isPlainObject } from 'lodash/fp'; +import type { SignalSource } from '../../../types'; + +/** + * Returns true if path in SignalSource object is valid + * Path is valid if each field in hierarchy is object or undefined + * Path is not valid if ANY of field in hierarchy is not object or undefined + * @param path in source to check within source + * @param source The source document + * @returns boolean + */ +export const isPathValid = (path: string, source: SignalSource): boolean => { + if (!path) { + return false; + } + const splitPath = path.split('.'); + + return splitPath.every((_, index, array) => { + const newPath = [...array].splice(0, index + 1).join('.'); + const valueToCheck = get(newPath, source); + return valueToCheck === undefined || isPlainObject(valueToCheck); + }); +}; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 67862f9e117ab..c5654f37517fb 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -36,6 +36,7 @@ "@kbn/actions-plugin", "@kbn/alerting-plugin", "@kbn/cases-plugin", + "@kbn/cloud-defend-plugin", "@kbn/cloud-experiments-plugin", "@kbn/cloud-security-posture-plugin", "@kbn/encrypted-saved-objects-plugin", diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index 7e61e0a9e5e08..a0875d9df4977 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -12,6 +12,7 @@ export enum SYNTHETICS_API_URLS { OVERVIEW_STATUS = `/internal/synthetics/overview_status`, INDEX_SIZE = `/internal/synthetics/index_size`, PARAMS = `/synthetics/params`, + PRIVATE_LOCATIONS = `/synthetics/private_locations`, SYNC_GLOBAL_PARAMS = `/synthetics/sync_global_params`, ENABLE_DEFAULT_ALERTING = `/synthetics/enable_default_alerting`, JOURNEY = `/internal/synthetics/journey/{checkGroup}`, diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts index 86b0b07052eac..cf09da6eb92fc 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts @@ -6,14 +6,26 @@ */ import * as t from 'io-ts'; +export const StateEndsCodec = t.type({ + duration_ms: t.union([t.string, t.number]), + checks: t.number, + ends: t.union([t.string, t.null]), + started_at: t.string, + id: t.string, + up: t.number, + down: t.number, + status: t.string, +}); export const ErrorStateCodec = t.type({ - duration_ms: t.string, + duration_ms: t.union([t.string, t.number]), checks: t.number, - ends: t.union([t.string, t.null]), + ends: t.union([StateEndsCodec, t.null]), started_at: t.string, id: t.string, up: t.number, down: t.number, status: t.string, }); + +export type ErrorState = t.TypeOf; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts index 8cb69f498b066..e9aec6dc3f1c4 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts @@ -149,4 +149,20 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => { await page.click('button:has-text("Delete location")'); await page.click('text=Create your first private location'); }); + + step('login with non super user', async () => { + await page.click('[data-test-subj="userMenuAvatar"]'); + await page.click('text="Log out"'); + await syntheticsApp.loginToKibana('viewer', 'changeme'); + }); + + step('viewer user cannot add locations', async () => { + await syntheticsApp.navigateToSettings(false); + await page.click('text=Private Locations'); + await page.waitForSelector( + `text="You're missing some Kibana privileges to manage private locations"` + ); + const createLocationBtn = await page.getByRole('button', { name: 'Create location' }); + expect(await createLocationBtn.getAttribute('disabled')).toEqual(''); + }); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts index ea8f70742f578..e9f931e64bf04 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts @@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => { await page.waitForSelector('text=Test run details'); await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=After 2.1 s'); + await page.waitForSelector('text=After 2.12 s'); }); }); diff --git a/x-pack/plugins/synthetics/e2e/page_objects/login.tsx b/x-pack/plugins/synthetics/e2e/page_objects/login.tsx index a8a55aadab31d..92c4de21ff154 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/login.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/login.tsx @@ -23,14 +23,6 @@ export function loginPageProvider({ await waitForLoadingToFinish({ page }); }, async loginToKibana(usernameT?: 'editor' | 'viewer', passwordT?: string) { - try { - // Close Monitor Management tour added in 8.2.0 - await page.addInitScript(() => { - window.localStorage.setItem('xpack.synthetics.monitorManagement.openTour', 'false'); - }); - } catch (e) { - // ignore - } if (isRemote) { await page.click('text="Log in with Elasticsearch"'); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx index 2bfd04fa26d57..9aa9d90e29ee9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx @@ -40,19 +40,25 @@ const DescriptionLabel = euiStyled(EuiDescriptionListDescription)` width: 60%; `; +export interface MonitorDetailsPanelProps { + latestPing?: Ping; + loading: boolean; + configId: string; + monitor: EncryptedSyntheticsSavedMonitor | null; + hideEnabled?: boolean; + hideLocations?: boolean; + hasBorder?: boolean; +} + export const MonitorDetailsPanel = ({ monitor, latestPing, loading, configId, hideEnabled = false, -}: { - latestPing?: Ping; - loading: boolean; - configId: string; - monitor: EncryptedSyntheticsSavedMonitor | null; - hideEnabled?: boolean; -}) => { + hideLocations = false, + hasBorder = true, +}: MonitorDetailsPanelProps) => { const dispatch = useDispatch(); if (!monitor) { @@ -60,7 +66,12 @@ export const MonitorDetailsPanel = ({ } return ( - + @@ -116,10 +127,15 @@ export const MonitorDetailsPanel = ({ {FREQUENCY_LABEL} {frequencyStr(monitor[ConfigKey.SCHEDULE])} - {LOCATIONS_LABEL} - - - + + {!hideLocations && ( + <> + {LOCATIONS_LABEL} + + + + + )} {TAGS_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx index e9094bbc9a3f0..97a9ac3e62ce3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx @@ -6,13 +6,14 @@ */ import React, { ReactNode } from 'react'; -import { EuiCallOut, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCallOut, EuiToolTip, EuiCode } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export const FleetPermissionsCallout = () => { return ( - -

    {NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

    + +

    {NEED_PRIVATE_LOCATIONS_PERMISSION}

    ); }; @@ -62,26 +63,32 @@ function getRestrictionReasonLabel( : undefined; } -export const NEED_PERMISSIONS = i18n.translate( - 'xpack.synthetics.monitorManagement.needPermissions', +export const NEED_PERMISSIONS_PRIVATE_LOCATIONS = i18n.translate( + 'xpack.synthetics.monitorManagement.privateLocations.needPermissions', { - defaultMessage: 'Need permissions', + defaultMessage: "You're missing some Kibana privileges to manage private locations", } ); -export const NEED_FLEET_READ_AGENT_POLICIES_PERMISSION = i18n.translate( - 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission', - { - defaultMessage: - 'You are not authorized to access Fleet. Fleet permissions are required to create new private locations.', - } +export const ALL = i18n.translate('xpack.synthetics.monitorManagement.priviledges.all', { + defaultMessage: 'All', +}); + +export const NEED_PRIVATE_LOCATIONS_PERMISSION = ( + {`"${ALL}"`}, + }} + /> ); export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.cannotSaveIntegration', { defaultMessage: - 'You are not authorized to update integrations. Integrations write permissions are required.', + 'You are not authorized to manage private locations. It requires the "All" Kibana privilege for both Fleet and Integrations.', } ); @@ -89,7 +96,7 @@ const CANNOT_PERFORM_ACTION_FLEET = i18n.translate( 'xpack.synthetics.monitorManagement.noFleetPermission', { defaultMessage: - 'You are not authorized to perform this action. Integrations write permissions are required.', + 'You are not authorized to perform this action. It requires the "All" Kibana privilege for Integrations.', } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index c90a00710f32f..a9341eae5fa69 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { CSSProperties, ReactElement, useState } from 'react'; +import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, @@ -62,25 +62,38 @@ export const BrowserStepsList = ({ Record >({}); - const toggleDetails = (item: JourneyStep) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item._id]) { - delete itemIdToExpandedRowMapValues[item._id]; - } else { - if (testNowMode) { - itemIdToExpandedRowMapValues[item._id] = ( - - - - - - ); - } else { - itemIdToExpandedRowMapValues[item._id] = <>; - } + const toggleDetails = useCallback( + (item: JourneyStep) => { + setItemIdToExpandedRowMap((prevState) => { + const itemIdToExpandedRowMapValues = { ...prevState }; + if (itemIdToExpandedRowMapValues[item._id]) { + delete itemIdToExpandedRowMapValues[item._id]; + } else { + if (testNowMode) { + itemIdToExpandedRowMapValues[item._id] = ( + + + + + + ); + } else { + itemIdToExpandedRowMapValues[item._id] = <>; + } + } + return itemIdToExpandedRowMapValues; + }); + }, + [steps, testNowMode] + ); + + const failedStep = stepEnds?.find((step) => step.synthetics.step?.status === 'failed'); + + useEffect(() => { + if (failedStep && showExpand) { + toggleDetails(failedStep); } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }; + }, [failedStep, showExpand, toggleDetails]); const columns: Array> = [ ...(showExpand diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/wrappers/service_allowed_wrapper.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/wrappers/service_allowed_wrapper.tsx index 163ed0d37b7c3..59474a8f618dc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/wrappers/service_allowed_wrapper.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/wrappers/service_allowed_wrapper.tsx @@ -27,7 +27,7 @@ export const ServiceAllowedWrapper: React.FC = ({ children }) => { return ( {MONITOR_MANAGEMENT_LABEL}} - body={

    {PUBLIC_BETA_DESCRIPTION}

    } + body={

    {ACCESS_RESTRICTED_MESSAGE}

    } actions={[ {REQUEST_ACCESS_LABEL} @@ -55,10 +55,9 @@ const LOADING_MONITOR_MANAGEMENT_LABEL = i18n.translate( } ); -export const PUBLIC_BETA_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.publicBetaDescription', +export const ACCESS_RESTRICTED_MESSAGE = i18n.translate( + 'xpack.synthetics.monitorManagement.accessRestricted', { - defaultMessage: - "We've got a brand new app on the way. In the meantime, we're excited to give you early access to our globally managed testing infrastructure. This will allow you to upload synthetic monitors using our new point and click script recorder and manage your monitors with a new UI.", + defaultMessage: 'Your access to globally managed testing infrastructure is restricted.', } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx index 9b65149be3ad0..bac7b09ecbf84 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; +import moment, { Moment } from 'moment'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; import { useErrorFailedTests } from '../hooks/use_last_error_state'; export const ErrorDuration: React.FC = () => { @@ -16,13 +17,41 @@ export const ErrorDuration: React.FC = () => { const state = failedTests?.[0]?.state; - const duration = state ? moment().diff(moment(state?.started_at), 'minutes') : 0; + const { killerState } = useFindMyKillerState(); - return ( - - ); + const endsAt = killerState?.timestamp ? moment(killerState?.timestamp) : moment(); + const startedAt = moment(state?.started_at); + + const duration = state ? getErrorDuration(startedAt, endsAt) : 0; + + return ; }; const ERROR_DURATION = i18n.translate('xpack.synthetics.errorDetails.errorDuration', { defaultMessage: 'Error duration', }); + +const getErrorDuration = (startedAt: Moment, endsAt: Moment) => { + // const endsAt = state.ends ? moment(state.ends) : moment(); + // const startedAt = moment(state?.started_at); + + const diffInDays = endsAt.diff(startedAt, 'days'); + if (diffInDays > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.days', { + defaultMessage: '{value} days', + values: { value: diffInDays }, + }); + } + const diffInHours = endsAt.diff(startedAt, 'hours'); + if (diffInHours > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: diffInHours }, + }); + } + const diffInMinutes = endsAt.diff(startedAt, 'minutes'); + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.mins', { + defaultMessage: '{value} mins', + values: { value: diffInMinutes }, + }); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx index 30461842e963f..d2e0b12793bc6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx @@ -5,8 +5,32 @@ * 2.0. */ import React from 'react'; +import { EuiLoadingContent } from '@elastic/eui'; +import moment from 'moment'; +import { Ping } from '../../../../../../common/runtime_types'; import { MonitorFailedTests } from '../../monitor_details/monitor_errors/failed_tests'; -export const ErrorTimeline = () => { - return ; +export const ErrorTimeline = ({ lastTestRun }: { lastTestRun?: Ping }) => { + if (!lastTestRun) { + return ; + } + const diff = moment(lastTestRun.monitor.timespan?.lt).diff( + moment(lastTestRun.monitor.timespan?.gte), + 'minutes' + ); + const startedAt = lastTestRun?.state?.started_at; + + return ( + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx index 75b1f9a31690b..76e8ca2ebaea1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx @@ -8,15 +8,13 @@ import React, { ReactElement } from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useErrorFailedTests } from '../hooks/use_last_error_state'; import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; export const ResolvedAt: React.FC = () => { - const { failedTests } = useErrorFailedTests(); + const { killerState } = useFindMyKillerState(); - const state = failedTests?.[0]?.state; - - let endsAt: string | ReactElement = useFormatTestRunAt(state?.ends ?? ''); + let endsAt: string | ReactElement = useFormatTestRunAt(killerState?.timestamp); if (!endsAt) { endsAt = 'N/A'; 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 3174d85776733..4a6ea2d3f34e1 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 @@ -21,7 +21,7 @@ import { FailedTestsList } from './components/failed_tests_list'; import { ErrorTimeline } from './components/error_timeline'; import { useErrorDetailsBreadcrumbs } from './hooks/use_error_details_breadcrumbs'; import { StepImage } from '../step_details_page/step_screenshot/step_image'; -import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel'; +import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel_container'; export function ErrorDetailsPage() { const { failedTests, loading } = useErrorFailedTests(); @@ -43,7 +43,7 @@ export function ErrorDetailsPage() { return (
    - + @@ -71,16 +71,22 @@ export function ErrorDetailsPage() { - - {data?.details?.journey && failedStep && ( - - )} - + {data?.details?.journey && failedStep && ( + <> + + + + + + )} - - +
    diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts index 61cef2b818615..c96df76b40456 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; import { useTestRunDetailsBreadcrumbs } from '../../test_run_details/hooks/use_test_run_details_breadcrumbs'; import { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor'; import { ConfigKey } from '../../../../../../common/runtime_types'; @@ -19,10 +20,14 @@ export const useErrorDetailsBreadcrumbs = ( const { monitor } = useSelectedMonitor(); + const selectedLocation = useSelectedLocation(); + const errorsBreadcrumbs = [ { text: ERRORS_CRUMB, - href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors`, + href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors?locationId=${ + selectedLocation?.id + }`, }, ...(extraCrumbs ?? []), ]; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx index 241c410038276..8b061d7c587f7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx @@ -57,7 +57,8 @@ export function useErrorFailedTests() { return useMemo(() => { const failedTests = data?.hits.hits?.map((doc) => { - return doc._source as Ping; + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; }) ?? []; return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts new file mode 100644 index 0000000000000..33f923c94d2ba --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts @@ -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 { useParams } from 'react-router-dom'; +import { useMemo } from 'react'; +import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; +import { Ping } from '../../../../../../common/runtime_types'; +import { + EXCLUDE_RUN_ONCE_FILTER, + SUMMARY_FILTER, +} from '../../../../../../common/constants/client_defaults'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { useSyntheticsRefreshContext } from '../../../contexts'; +import { useGetUrlParams } from '../../../hooks'; + +export function useFindMyKillerState() { + const { lastRefresh } = useSyntheticsRefreshContext(); + + const { errorStateId, monitorId } = useParams<{ errorStateId: string; monitorId: string }>(); + + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + + const { data, loading } = useReduxEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + + body: { + // TODO: remove this once we have a better way to handle this mapping + runtime_mappings: { + 'state.ends.id': { + type: 'keyword', + }, + }, + size: 1, + query: { + bool: { + filter: [ + SUMMARY_FILTER, + EXCLUDE_RUN_ONCE_FILTER, + { + term: { + 'state.ends.id': errorStateId, + }, + }, + { + term: { + config_id: monitorId, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': 'desc' }], + }, + }, + [lastRefresh, monitorId, dateRangeStart, dateRangeEnd], + { name: 'getStateWhichEndTheState' } + ); + + return useMemo(() => { + const killerStates = + data?.hits.hits?.map((doc) => { + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; + }) ?? []; + + return { + loading, + killerState: killerStates?.[0], + }; + }, [data, loading]); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx index c53de93fa5123..3e2d0f39000fe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx @@ -39,7 +39,7 @@ export const getErrorDetailsRouteConfig = ( ), rightSideItems: [ , - , + , , , ], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx index 6f6052af64a3f..e45e64dab1c15 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx @@ -87,12 +87,15 @@ export function useMonitorErrors(monitorIdArg?: string) { }, }, }, - [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd], - { name: 'getMonitorErrors', isRequestReady: Boolean(selectedLocation?.label) } + [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd, selectedLocation?.label], + { + name: `getMonitorErrors/${dateRangeStart}/${dateRangeEnd}`, + isRequestReady: Boolean(selectedLocation?.label), + } ); return useMemo(() => { - const errorStates = (data?.aggregations?.errorStates.buckets ?? []).map((loc) => { + const errorStates = data?.aggregations?.errorStates.buckets?.map((loc) => { return loc.summary.hits.hits?.[0]._source as PingState; }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx index ecd1f6141c7f4..d8fb9cc827fcd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx @@ -50,6 +50,10 @@ export const ErrorsList = ({ const selectedLocation = useSelectedLocation(); + const lastTestRun = errorStates?.sort((a, b) => { + return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf(); + })?.[0]; + const columns = [ { field: 'item.state.started_at', @@ -67,49 +71,56 @@ export const ErrorsList = ({ /> ); const isActive = isActiveState(item); - if (!isActive) { + if (!isActive || lastTestRun.state.id !== item.state.id) { return link; } return ( - {link} + + {link} + - Active + {ACTIVE_LABEL} ); }, }, + ...(isBrowserType + ? [ + { + field: 'monitor.check_group', + name: FAILED_STEP_LABEL, + truncateText: true, + sortable: (a: PingState) => { + const failedStep = failedSteps.find( + (step) => step.monitor.check_group === a.monitor.check_group + ); + if (!failedStep) { + return a.monitor.check_group; + } + return failedStep.synthetics?.step?.name; + }, + render: (value: string, item: PingState) => { + const failedStep = failedSteps.find((step) => step.monitor.check_group === value); + if (!failedStep) { + return <>--; + } + return ( + + {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} + + ); + }, + }, + ] + : []), { - field: 'monitor.check_group', - name: !isBrowserType ? ERROR_MESSAGE_LABEL : FAILED_STEP_LABEL, - truncateText: true, - sortable: (a: PingState) => { - const failedStep = failedSteps.find( - (step) => step.monitor.check_group === a.monitor.check_group - ); - if (!failedStep) { - return a.monitor.check_group; - } - return failedStep.synthetics?.step?.name; - }, - render: (value: string, item: PingState) => { - if (!isBrowserType) { - return {item.error.message ?? '--'}; - } - const failedStep = failedSteps.find((step) => step.monitor.check_group === value); - if (!failedStep) { - return <>--; - } - return ( - - {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} - - ); - }, + field: 'error.message', + name: ERROR_MESSAGE_LABEL, }, { field: 'state.duration_ms', @@ -157,6 +168,7 @@ export const ErrorsList = ({
    { +export const MonitorFailedTests = ({ + time, + allowBrushing = true, +}: { + time: { to: string; from: string }; + allowBrushing?: boolean; +}) => { const { observability } = useKibana().services; const { ExploratoryViewEmbeddable } = observability; @@ -41,7 +47,8 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string { time, reportDefinitions: { - ...(monitorId ? { 'monitor.id': [monitorId] } : { 'state.id': [errorStateId] }), + ...(monitorId ? { 'monitor.id': [monitorId] } : {}), + ...(errorStateId ? { 'state.id': [errorStateId] } : {}), }, dataType: 'synthetics', selectedMetricField: 'failed_tests', @@ -49,21 +56,25 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string }, ]} onBrushEnd={({ range }) => { - updateUrl({ - dateRangeStart: moment(range[0]).toISOString(), - dateRangeEnd: moment(range[1]).toISOString(), - }); + if (allowBrushing) { + updateUrl({ + dateRangeStart: moment(range[0]).toISOString(), + dateRangeEnd: moment(range[1]).toISOString(), + }); + } }} /> {FAILED_TESTS_LABEL} - - - {BRUSH_LABEL} - - + {allowBrushing && ( + + + {BRUSH_LABEL} + + + )} ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx index 4d2794e9da995..04930af018152 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx @@ -23,9 +23,9 @@ import { ErrorsTabContent } from './errors_tab_content'; export const MonitorErrors = () => { const { errorStates, loading, data } = useMonitorErrors(); - const initialLoading = loading && !data; + const initialLoading = !data; - const emptyState = !loading && errorStates.length === 0; + const emptyState = !loading && errorStates && errorStates?.length === 0; const redirect = useMonitorDetailsPage(); if (redirect) { @@ -39,7 +39,7 @@ export const MonitorErrors = () => { {initialLoading && } {emptyState && }
    - +
    ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx similarity index 85% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx index c2a26f5242f0f..99885ac30fb87 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx @@ -9,12 +9,15 @@ import React from 'react'; import { EuiLoadingContent } from '@elastic/eui'; import { useParams } from 'react-router-dom'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { MonitorDetailsPanel } from '../../common/components/monitor_details_panel'; +import { + MonitorDetailsPanelProps, + MonitorDetailsPanel, +} from '../../common/components/monitor_details_panel'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { ConfigKey } from '../../../../../../common/runtime_types'; import { useMonitorLatestPing } from '../hooks/use_monitor_latest_ping'; -export const MonitorDetailsPanelContainer = () => { +export const MonitorDetailsPanelContainer = (props: Partial) => { const { latestPing } = useMonitorLatestPing(); const { monitorId: configId } = useParams<{ monitorId: string }>(); @@ -34,6 +37,7 @@ export const MonitorDetailsPanelContainer = () => { monitor={monitor} loading={loading} configId={configId} + {...props} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index b087da10f767c..ebaaee6e44e50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -21,7 +21,7 @@ import { MonitorDurationTrend } from './duration_trend'; import { StepDurationPanel } from './step_duration_panel'; import { AvailabilityPanel } from './availability_panel'; import { DurationPanel } from './duration_panel'; -import { MonitorDetailsPanelContainer } from './monitor_details_panel'; +import { MonitorDetailsPanelContainer } from './monitor_details_panel_container'; import { AvailabilitySparklines } from './availability_sparklines'; import { LastTestRun } from './last_test_run'; import { LAST_10_TEST_RUNS, TestRunsTable } from './test_runs_table'; 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 4749e3c810116..7e19abbf758ce 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 @@ -9,16 +9,33 @@ import React from 'react'; import { EuiFilterGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSelector } from 'react-redux'; +import { useGetUrlParams } from '../../../../hooks'; import { selectServiceLocationsState } from '../../../../state'; import { SyntheticsMonitorFilterItem, getSyntheticsFilterDisplayValues, SyntheticsMonitorFilterChangeHandler, + LabelWithCountValue, } from './filter_fields'; import { useFilters } from './use_filters'; import { FilterButton } from './filter_button'; +const mixUrlValues = ( + values?: LabelWithCountValue[], + urlLabels?: string[] +): LabelWithCountValue[] => { + const urlValues = urlLabels?.map((label) => ({ label, count: 0 })) ?? []; + const newValues = [...(values ?? [])]; + // add url values that are not in the values + urlValues.forEach((urlValue) => { + if (!newValues.find((value) => value.label === urlValue.label)) { + newValues.push(urlValue); + } + }); + return newValues; +}; + export const FilterGroup = ({ handleFilterChange, }: { @@ -28,26 +45,44 @@ export const FilterGroup = ({ const { locations } = useSelector(selectServiceLocationsState); + const urlParams = useGetUrlParams(); + const filters: SyntheticsMonitorFilterItem[] = [ { label: TYPE_LABEL, field: 'monitorTypes', - values: getSyntheticsFilterDisplayValues(data.monitorTypes, 'monitorTypes', locations), + values: getSyntheticsFilterDisplayValues( + mixUrlValues(data.monitorTypes, urlParams.monitorTypes), + 'monitorTypes', + locations + ), }, { label: LOCATION_LABEL, field: 'locations', - values: getSyntheticsFilterDisplayValues(data.locations, 'locations', locations), + values: getSyntheticsFilterDisplayValues( + mixUrlValues(data.locations, urlParams.locations), + 'locations', + locations + ), }, { label: TAGS_LABEL, field: 'tags', - values: getSyntheticsFilterDisplayValues(data.tags, 'tags', locations), + values: getSyntheticsFilterDisplayValues( + mixUrlValues(data.tags, urlParams.tags), + 'tags', + locations + ), }, { label: SCHEDULE_LABEL, field: 'schedules', - values: getSyntheticsFilterDisplayValues(data.schedules, 'schedules', locations), + values: getSyntheticsFilterDisplayValues( + mixUrlValues(data.schedules, urlParams.schedules), + 'schedules', + locations + ), }, ]; @@ -55,7 +90,11 @@ export const FilterGroup = ({ filters.push({ label: PROJECT_LABEL, field: 'projects', - values: getSyntheticsFilterDisplayValues(data.projects, 'projects', locations), + values: getSyntheticsFilterDisplayValues( + mixUrlValues(data.projects, urlParams.projects), + 'projects', + locations + ), }); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx index 4eec46469ca06..45de1899537ea 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx @@ -5,14 +5,14 @@ * 2.0. */ -import React, { memo } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FilterGroup } from './filter_group'; import { SearchField } from '../search_field'; import { SyntheticsMonitorFilterChangeHandler } from './filter_fields'; -export const ListFilters = memo(function ({ +export const ListFilters = function ({ handleFilterChange, }: { handleFilterChange: SyntheticsMonitorFilterChangeHandler; @@ -27,4 +27,4 @@ export const ListFilters = memo(function ({ ); -}); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx index b34f4f6fe421a..187f590c9b360 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx @@ -325,6 +325,7 @@ export function MonitorDetailFlyout(props: Props) { { setIsOpen(false); @@ -69,9 +69,12 @@ export const AddLocationFlyout = ({ - {!canReadAgentPolicies && } + {!canManagePrivateLocation && } - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx index 42aa5e8208ece..b3ad93e26d041 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { LEARN_MORE, READ_DOCS } from './empty_locations'; -export const AgentPolicyNeeded = () => { +export const AgentPolicyNeeded = ({ disabled }: { disabled: boolean }) => { const { basePath } = useSyntheticsSettingsContext(); return ( @@ -20,7 +20,12 @@ export const AgentPolicyNeeded = () => { title={

    {AGENT_POLICY_NEEDED}

    } body={

    {ADD_AGENT_POLICY_DESCRIPTION}

    } actions={ - + {CREATE_AGENT_POLICY} } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx index 6d2c95cac70ae..959520c911469 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiConfirmModal, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useFleetPermissions } from '../../../hooks'; +import { useFleetPermissions, useCanManagePrivateLocation } from '../../../hooks'; import { CANNOT_SAVE_INTEGRATION_LABEL } from '../../common/components/permissions'; export const DeleteLocation = ({ @@ -30,6 +30,7 @@ export const DeleteLocation = ({ const { canSave } = useSyntheticsSettingsContext(); const { canSaveIntegrations } = useFleetPermissions(); + const canManagePrivateLocation = useCanManagePrivateLocation(); const [isModalOpen, setIsModalOpen] = useState(false); @@ -62,7 +63,9 @@ export const DeleteLocation = ({ return ( <> {isModalOpen && deleteModal} - + { setIsModalOpen(true); }} - isDisabled={!canDelete || !canSave} + isDisabled={!canDelete || !canManagePrivateLocation || !canSave} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx index e343e9faf1aec..ce80c7d97e71e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx @@ -5,12 +5,25 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import { defaultCore, WrappedHelper } from '../../../../utils/testing'; - +import { renderHook, act } from '@testing-library/react-hooks'; +import { WrappedHelper } from '../../../../utils/testing'; +import { getServiceLocations } from '../../../../state/service_locations'; +import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; import { useLocationsAPI } from './use_locations_api'; +import * as locationAPI from '../../../../state/private_locations/api'; +import * as reduxHooks from 'react-redux'; describe('useLocationsAPI', () => { + const dispatch = jest.fn(); + const addAPI = jest.spyOn(locationAPI, 'addSyntheticsPrivateLocations').mockResolvedValue({ + locations: [], + }); + const deletedAPI = jest.spyOn(locationAPI, 'deleteSyntheticsPrivateLocations').mockResolvedValue({ + locations: [], + }); + const getAPI = jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations'); + jest.spyOn(reduxHooks, 'useDispatch').mockReturnValue(dispatch); + it('returns expected results', () => { const { result } = renderHook(() => useLocationsAPI(), { wrapper: WrappedHelper, @@ -22,20 +35,15 @@ describe('useLocationsAPI', () => { privateLocations: [], }) ); - expect(defaultCore.savedObjects.client.get).toHaveBeenCalledWith( - 'synthetics-privates-locations', - 'synthetics-privates-locations-singleton' - ); + expect(getAPI).toHaveBeenCalledTimes(1); }); - defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ - attributes: { - locations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - ], - }, + jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations').mockResolvedValue({ + locations: [ + { + id: 'Test', + agentPolicyId: 'testPolicy', + } as any, + ], }); it('returns expected results after data', async () => { const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { @@ -71,77 +79,50 @@ describe('useLocationsAPI', () => { await waitForNextUpdate(); - result.current.onSubmit({ - id: 'new', - agentPolicyId: 'newPolicy', - label: 'new', + act(() => { + result.current.onSubmit({ + id: 'new', + agentPolicyId: 'newPolicy', + label: 'new', + concurrentMonitors: 1, + geo: { + lat: 0, + lon: 0, + }, + }); + }); + + await waitForNextUpdate(); + + expect(addAPI).toHaveBeenCalledWith({ concurrentMonitors: 1, + id: 'newPolicy', geo: { lat: 0, lon: 0, }, + label: 'new', + agentPolicyId: 'newPolicy', }); - - await waitForNextUpdate(); - - expect(defaultCore.savedObjects.client.create).toHaveBeenCalledWith( - 'synthetics-privates-locations', - { - locations: [ - { id: 'Test', agentPolicyId: 'testPolicy' }, - { - concurrentMonitors: 1, - id: 'newPolicy', - geo: { - lat: 0, - lon: 0, - }, - label: 'new', - agentPolicyId: 'newPolicy', - }, - ], - }, - { id: 'synthetics-privates-locations-singleton', overwrite: true } - ); + expect(dispatch).toBeCalledWith(setAddingNewPrivateLocation(false)); + expect(dispatch).toBeCalledWith(getServiceLocations()); }); it('deletes location on delete', async () => { - defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ - attributes: { - locations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - { - id: 'Test1', - agentPolicyId: 'testPolicy1', - }, - ], - }, - }); - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { wrapper: WrappedHelper, }); await waitForNextUpdate(); - result.current.onDelete('Test'); + act(() => { + result.current.onDelete('Test'); + }); await waitForNextUpdate(); - expect(defaultCore.savedObjects.client.create).toHaveBeenLastCalledWith( - 'synthetics-privates-locations', - { - locations: [ - { - id: 'Test1', - agentPolicyId: 'testPolicy1', - }, - ], - }, - { id: 'synthetics-privates-locations-singleton', overwrite: true } - ); + expect(deletedAPI).toHaveBeenLastCalledWith('Test'); + expect(dispatch).toBeCalledWith(setAddingNewPrivateLocation(false)); + expect(dispatch).toBeCalledWith(getServiceLocations()); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts index 6b35e79152c87..8678328445a62 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts @@ -7,12 +7,13 @@ import { useFetcher } from '@kbn/observability-plugin/public'; import { useState } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useDispatch } from 'react-redux'; +import { getServiceLocations } from '../../../../state/service_locations'; import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; import { + addSyntheticsPrivateLocations, + deleteSyntheticsPrivateLocations, getSyntheticsPrivateLocations, - setSyntheticsPrivateLocations, } from '../../../../state/private_locations/api'; import { PrivateLocation } from '../../../../../../../common/runtime_types'; @@ -21,31 +22,29 @@ export const useLocationsAPI = () => { const [deleteId, setDeleteId] = useState(); const [privateLocations, setPrivateLocations] = useState([]); - const { savedObjects } = useKibana().services; - const dispatch = useDispatch(); const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); const { loading: fetchLoading } = useFetcher(async () => { - const result = await getSyntheticsPrivateLocations(savedObjects?.client!); - setPrivateLocations(result); + const result = await getSyntheticsPrivateLocations(); + setPrivateLocations(result.locations); return result; }, []); const { loading: saveLoading } = useFetcher(async () => { - if (privateLocations && formData) { - const existingLocations = privateLocations.filter((loc) => loc.id !== formData.agentPolicyId); - - const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { - locations: [...(existingLocations ?? []), { ...formData, id: formData.agentPolicyId }], + if (formData) { + const result = await addSyntheticsPrivateLocations({ + ...formData, + id: formData.agentPolicyId, }); setPrivateLocations(result.locations); setFormData(undefined); setIsAddingNew(false); + dispatch(getServiceLocations()); return result; } - }, [formData, privateLocations]); + }, [formData]); const onSubmit = (data: PrivateLocation) => { setFormData(data); @@ -57,14 +56,13 @@ export const useLocationsAPI = () => { const { loading: deleteLoading } = useFetcher(async () => { if (deleteId) { - const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { - locations: (privateLocations ?? []).filter((loc) => loc.id !== deleteId), - }); + const result = await deleteSyntheticsPrivateLocations(deleteId); setPrivateLocations(result.locations); setDeleteId(undefined); + dispatch(getServiceLocations()); return result; } - }, [deleteId, privateLocations]); + }, [deleteId]); return { formData, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx index 89fe466d241f1..a001c503697b1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx @@ -26,13 +26,17 @@ import { selectAgentPolicies } from '../../../state/private_locations'; export const LocationForm = ({ privateLocations, + hasPermissions, }: { onDiscard?: () => void; privateLocations: PrivateLocation[]; + hasPermissions: boolean; }) => { const { data } = useSelector(selectAgentPolicies); - const { control, register } = useFormContext(); + const { control, register, watch } = useFormContext(); const { errors } = useFormState(); + const selectedPolicyId = watch('agentPolicyId'); + const selectedPolicy = data?.items.find((item) => item.id === selectedPolicyId); const tagsList = privateLocations.reduce((acc, item) => { const tags = item.tags || []; @@ -41,7 +45,7 @@ export const LocationForm = ({ return ( <> - {data?.items.length === 0 && } + {data?.items.length === 0 && } + + + {selectedPolicy?.agents === 0 && ( + +

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

    +
    + )}
    ); @@ -107,6 +144,13 @@ export const AGENT_CALLOUT_TITLE = i18n.translate( } ); +export const AGENT_MISSING_CALLOUT_TITLE = i18n.translate( + 'xpack.synthetics.monitorManagement.agentMissingCallout.title', + { + defaultMessage: 'Selected agent policy has no agents', + } +); + export const LOCATION_NAME_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.locationName', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx index d5246581b2176..1f7ba4a0b37ea 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx @@ -22,7 +22,7 @@ import { ViewLocationMonitors } from './view_location_monitors'; import { TableTitle } from '../../common/components/table_title'; import { TAGS_LABEL } from '../components/tags_field'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useFleetPermissions } from '../../../hooks'; +import { useCanManagePrivateLocation } from '../../../hooks'; import { setAddingNewPrivateLocation } from '../../../state/private_locations'; import { PrivateLocationDocsLink, START_ADDING_LOCATIONS_DESCRIPTION } from './empty_locations'; import { PrivateLocation } from '../../../../../../common/runtime_types'; @@ -53,7 +53,7 @@ export const PrivateLocationsTable = ({ const { locationMonitors, loading } = useLocationMonitors(); const { canSave } = useSyntheticsSettingsContext(); - const { canSaveIntegrations } = useFleetPermissions(); + const canManagePrivateLocations = useCanManagePrivateLocation(); const tagsList = privateLocations.reduce((acc, item) => { const tags = item.tags || []; @@ -133,10 +133,10 @@ export const PrivateLocationsTable = ({ fill data-test-subj={'addPrivateLocationButton'} isLoading={loading} - disabled={!canSaveIntegrations || !canSave} + disabled={!canManagePrivateLocations || !canSave} onClick={() => setIsAddingNew(true)} iconType="plusInCircle" - title={!canSaveIntegrations ? CANNOT_SAVE_INTEGRATION_LABEL : undefined} + title={!canManagePrivateLocations ? CANNOT_SAVE_INTEGRATION_LABEL : undefined} > {ADD_LABEL}
    , diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx index 9cc313a106c96..92768f48a83ef 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx @@ -20,7 +20,7 @@ export const ManageEmptyState: FC<{ const { data: agentPolicies } = useSelector(selectAgentPolicies); if (agentPolicies?.total === 0) { - return ; + return ; } if (privateLocations.length === 0) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx new file mode 100644 index 0000000000000..b4e406353f2a1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '../../../utils/testing/rtl_helpers'; +import * as permissionsHooks from '../../../hooks'; +import * as locationHooks from './hooks/use_locations_api'; +import * as settingsHooks from '../../../contexts/synthetics_settings_context'; +import type { SyntheticsSettingsContextValues } from '../../../contexts'; +import { ManagePrivateLocations } from './manage_private_locations'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; + +jest.mock('../../../hooks'); +jest.mock('./hooks/use_locations_api'); +jest.mock('../../../contexts/synthetics_settings_context'); + +describe('', () => { + beforeEach(() => { + jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true); + jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + formData: {} as PrivateLocation, + loading: false, + onSubmit: jest.fn(), + privateLocations: [], + onDelete: jest.fn(), + deleteLoading: false, + }); + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave: true, + } as SyntheticsSettingsContextValues); + }); + + it.each([true, false])( + 'handles no agent found when the user does and does not have permissions', + (hasFleetPermissions) => { + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [], + total: 0, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText('No agent policies found')).toBeInTheDocument(); + + if (hasFleetPermissions) { + const button = getByRole('link', { name: 'Create agent policy' }); + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + const button = getByRole('button', { name: 'Create agent policy' }); + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); + + it.each([true, false])( + 'handles create first location when the user does and does not have permissions', + (hasFleetPermissions) => { + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [{}], + total: 1, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText('Create your first private location')).toBeInTheDocument(); + const button = getByRole('button', { name: 'Create location' }); + + if (hasFleetPermissions) { + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); + + it.each([true, false])( + 'handles location table when the user does and does not have permissions', + (hasFleetPermissions) => { + const privateLocationName = 'Test private location'; + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + jest.spyOn(permissionsHooks, 'useFleetPermissions').mockReturnValue({ + canSaveIntegrations: hasFleetPermissions, + canReadAgentPolicies: hasFleetPermissions, + }); + jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + formData: {} as PrivateLocation, + loading: false, + onSubmit: jest.fn(), + privateLocations: [ + { + label: privateLocationName, + id: 'lkjlere', + agentPolicyId: 'lkjelrje', + isServiceManaged: false, + concurrentMonitors: 2, + }, + ], + onDelete: jest.fn(), + deleteLoading: false, + }); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [{}], + total: 1, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText(privateLocationName)).toBeInTheDocument(); + const button = getByRole('button', { name: 'Create location' }); + + if (hasFleetPermissions) { + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx index d697d011e5841..e8246aa13221e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx @@ -6,9 +6,10 @@ */ import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { EuiSpacer } from '@elastic/eui'; import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; import { PrivateLocationsTable } from './locations_table'; -import { useFleetPermissions } from '../../../hooks'; +import { useCanManagePrivateLocation } from '../../../hooks'; import { ManageEmptyState } from './manage_empty_state'; import { AddLocationFlyout } from './add_location_flyout'; import { useLocationsAPI } from './hooks/use_locations_api'; @@ -25,12 +26,11 @@ export const ManagePrivateLocations = () => { const dispatch = useDispatch(); const isAddingNew = useSelector(selectAddingNewPrivateLocation); - const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = useLocationsAPI(); - const { canReadAgentPolicies } = useFleetPermissions(); + const canManagePrivateLocation = useCanManagePrivateLocation(); useEffect(() => { dispatch(getAgentPoliciesAction.get()); @@ -43,7 +43,12 @@ export const ManagePrivateLocations = () => { return ( <> - {!canReadAgentPolicies && } + {!canManagePrivateLocation && ( + <> + + + + )} {loading ? ( @@ -51,7 +56,7 @@ export const ManagePrivateLocations = () => { { return ( -

    - {canReadAgentPolicies && ( - - {policy ? ( - - {policy?.name} - - ) : ( - - {POLICY_IS_DELETED} - - )} - - )} -

    + {canReadAgentPolicies ? ( + + {policy ? ( + + {policy?.name} + + ) : ( + + {POLICY_IS_DELETED} + + )} + + ) : ( + agentPolicyId + )} +     + + {AGENTS_LABEL} + {policy?.agents} +
    ); }; @@ -50,3 +55,7 @@ export const PolicyName = ({ agentPolicyId }: { agentPolicyId: string }) => { const POLICY_IS_DELETED = i18n.translate('xpack.synthetics.monitorManagement.deletedPolicy', { defaultMessage: 'Policy is deleted', }); + +const AGENTS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agents', { + defaultMessage: 'Agents: ', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts index ee9b6c34bd8f9..51ddf14ee5f67 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts @@ -17,32 +17,27 @@ export const getSettingsRouteConfig = ( syntheticsPath: string, baseTitle: string ) => { + const sharedProps = { + title: i18n.translate('xpack.synthetics.settingsRoute.title', { + defaultMessage: 'Settings | {baseTitle}', + values: { baseTitle }, + }), + component: SettingsPage, + pageHeader: getSettingsPageHeader(history, syntheticsPath), + dataTestSubj: 'syntheticsSettingsPage', + pageSectionProps: { + paddingSize: 'm', + }, + }; + return [ { - title: i18n.translate('xpack.synthetics.settingsRoute.title', { - defaultMessage: 'Settings | {baseTitle}', - values: { baseTitle }, - }), + ...sharedProps, path: SETTINGS_ROUTE, - component: SettingsPage, - dataTestSubj: 'syntheticsSettingsPage', - pageSectionProps: { - paddingSize: 'm', - }, - pageHeader: getSettingsPageHeader(history, syntheticsPath), }, { - title: i18n.translate('xpack.synthetics.settingsRoute.title', { - defaultMessage: 'Settings | {baseTitle}', - values: { baseTitle }, - }), + ...sharedProps, path: SYNTHETICS_SETTINGS_ROUTE, - component: SettingsPage, - dataTestSubj: 'syntheticsSettingsPage', - pageSectionProps: { - paddingSize: 'm', - }, - pageHeader: getSettingsPageHeader(history, syntheticsPath), }, ] as RouteProps[]; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx index e05211ce69830..a063c5bbabe98 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx @@ -11,7 +11,7 @@ import moment from 'moment'; import { FormattedMessage } from '@kbn/i18n-react'; import { useParams } from 'react-router-dom'; import { TestRunErrorInfo } from './components/test_run_error_info'; -import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel'; +import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel_container'; import { useSelectedLocation } from '../monitor_details/hooks/use_selected_location'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; import { StepNumberNav } from './components/step_number_nav'; @@ -89,7 +89,7 @@ export const TestRunDetails = () => { - +
    )} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts index bbd5aa4f681bf..2ed8af08891ab 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts @@ -31,6 +31,12 @@ export function useCanUpdatePrivateMonitor(monitor: EncryptedSyntheticsMonitor) return canUpdatePrivateMonitor(monitor, canSaveIntegrations); } +export function useCanManagePrivateLocation() { + const { canSaveIntegrations, canReadAgentPolicies } = useFleetPermissions(); + + return Boolean(canSaveIntegrations && canReadAgentPolicies); +} + export function canUpdatePrivateMonitor( monitor: EncryptedSyntheticsMonitor, canSaveIntegrations: boolean diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts index 12a2ed7043973..2e2e0f1254acf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts @@ -19,5 +19,5 @@ export const useSyntheticsServiceAllowed = () => { // return useSelector(syntheticsServiceAllowedSelector); // TODO Implement for Synthetics App - return { isAllowed: true, signupUrl: 'https://example.com', loading: false }; + return { isAllowed: true, signupUrl: undefined, loading: false }; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index 378855def3ef7..bbd0d70afd339 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -118,8 +118,8 @@ export const monitorListReducer = createReducer(initialState, (builder) => { delete state.monitorUpsertStatuses[action.payload]; } }) - .addCase(cleanMonitorListState, () => { - return initialState; + .addCase(cleanMonitorListState, (state) => { + return { ...initialState, pageState: state.pageState }; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts index 3c48696c685b9..50683fdd87a35 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/public'; -import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types'; +import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; +import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types'; import { apiService } from '../../../../utils/api_service/api_service'; import { AgentPoliciesList } from '.'; -import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, -} from '../../../../../common/saved_objects/private_locations'; const FLEET_URLS = { AGENT_POLICIES: '/api/fleet/agent_policies', @@ -33,26 +29,18 @@ export const fetchAgentPolicies = async (): Promise => { ); }; -export const setSyntheticsPrivateLocations = async ( - client: SavedObjectsClientContract, - privateLocations: SyntheticsPrivateLocations -) => { - const result = await client.create(privateLocationsSavedObjectName, privateLocations, { - id: privateLocationsSavedObjectId, - overwrite: true, - }); +export const addSyntheticsPrivateLocations = async ( + newLocation: PrivateLocation +): Promise => { + return await apiService.post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, newLocation); +}; - return result.attributes; +export const getSyntheticsPrivateLocations = async (): Promise => { + return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS); }; -export const getSyntheticsPrivateLocations = async (client: SavedObjectsClientContract) => { - try { - const obj = await client.get( - privateLocationsSavedObjectName, - privateLocationsSavedObjectId - ); - return obj?.attributes.locations ?? []; - } catch (getErr) { - return []; - } +export const deleteSyntheticsPrivateLocations = async ( + locationId: string +): Promise => { + return await apiService.delete(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + `/${locationId}`); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index ced9c212e83cf..371e054476c79 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -45,7 +45,6 @@ export const rootEffect = function* root(): Generator { fork(fetchAgentPoliciesEffect), fork(fetchDynamicSettingsEffect), fork(setDynamicSettingsEffect), - fork(fetchAgentPoliciesEffect), fork(fetchAlertConnectorsEffect), fork(syncGlobalParamsEffect), fork(enableDefaultAlertingEffect), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts index 5337468f6e730..b1e808d748d76 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts @@ -9,16 +9,16 @@ import { formatTestDuration } from './test_time_formats'; describe('formatTestDuration', () => { it.each` - duration | expected | isMilli - ${undefined} | ${'0 ms'} | ${undefined} - ${120_000_000} | ${'2 min'} | ${undefined} - ${6_200_000} | ${'6.2 s'} | ${false} - ${500_000} | ${'500 ms'} | ${undefined} - ${100} | ${'0 ms'} | ${undefined} - ${undefined} | ${'0 ms'} | ${true} - ${600_000} | ${'10 min'} | ${true} - ${6_200} | ${'6.2 s'} | ${true} - ${500} | ${'500 ms'} | ${true} + duration | expected | isMilli + ${undefined} | ${'0 ms'} | ${undefined} + ${120_000_000} | ${'2 mins'} | ${undefined} + ${6_200_000} | ${'6.2 sec'} | ${false} + ${500_000} | ${'500 ms'} | ${undefined} + ${100} | ${'0 ms'} | ${undefined} + ${undefined} | ${'0 ms'} | ${true} + ${600_000} | ${'10 mins'} | ${true} + ${6_200} | ${'6.2 sec'} | ${true} + ${500} | ${'500 ms'} | ${true} `( 'returns $expected when `duration` is $duration and `isMilli` $isMilli', ({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts index 5d605ad4c2192..efae8b1652738 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts @@ -6,6 +6,7 @@ */ import moment from 'moment'; +import { i18n } from '@kbn/i18n'; import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; /** @@ -16,19 +17,40 @@ import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; export const formatTestDuration = (duration = 0, isMilli = false) => { const secs = isMilli ? duration / 1e3 : duration / 1e6; + const hours = Math.floor(secs / 3600); + + if (hours >= 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: hours }, + }); + } + if (secs >= 60) { - return `${parseFloat((secs / 60).toFixed(1))} min`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.minutes', { + defaultMessage: '{value} mins', + values: { value: parseFloat((secs / 60).toFixed(1)) }, + }); } if (secs >= 1) { - return `${parseFloat(secs.toFixed(1))} s`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.seconds', { + defaultMessage: '{value} sec', + values: { value: parseFloat(secs.toFixed(1)) }, + }); } if (isMilli) { - return `${duration.toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.milliseconds', { + defaultMessage: '{value} ms', + values: { value: duration.toFixed(0) }, + }); } - return `${(duration / 1000).toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.microseconds', { + defaultMessage: '{value} ms', + values: { value: (duration / 1000).toFixed(0) }, + }); }; export function formatTestRunAt(timestamp: string, format: string) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx index f8447d1cb2741..627f89b718742 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx @@ -5,89 +5,34 @@ * 2.0. */ -import { EuiButton, EuiHeaderLink, EuiLink, EuiSpacer, EuiTourStep, EuiText } from '@elastic/eui'; +import { EuiHeaderLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; import { MONITOR_MANAGEMENT_ROUTE } from '../../../../../common/constants'; -import { PUBLIC_BETA_DESCRIPTION } from '../../../pages/monitor_management/service_allowed_wrapper'; export const ManageMonitorsBtn = () => { - const [isOpen, setIsOpen] = useLocalStorage('xpack.synthetics.monitorManagement.openTour', true); - const history = useHistory(); return ( - - -

    {PUBLIC_BETA_DESCRIPTION}

    -
    - - - {MONITOR_MANAGEMENT_LABEL} - - - } - isStepOpen={isOpen} - onFinish={() => setIsOpen(false)} - step={1} - stepsTotal={1} - subtitle={NEW_LABEL} - title={GETTING_STARTED_LABEL} - anchorPosition="upCenter" - maxWidth={416} - footerAction={ - setIsOpen(false)}> - {DISMISS_LABEL} - - } + - - - -
    + + ); }; -const GETTING_STARTED_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.gettingStarted.label', - { - defaultMessage: 'Get started with Synthetic Monitoring', - } -); - -const MONITOR_MANAGEMENT_LABEL = i18n.translate('xpack.synthetics.monitorManagement.try.label', { - defaultMessage: 'Try Monitor Management', -}); -const DISMISS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.try.dismiss', { - defaultMessage: 'Dismiss', -}); - const NAVIGATE_LABEL = i18n.translate('xpack.synthetics.page_header.manageLink.label', { defaultMessage: 'Navigate to the Uptime Monitor Management page', }); - -const NEW_LABEL = i18n.translate('xpack.synthetics.monitorManagement.new.label', { - defaultMessage: 'New', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx index 103a9a37480db..805097312d4e8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx @@ -164,7 +164,7 @@ export const NEED_PERMISSIONS = i18n.translate( ); export const NEED_FLEET_READ_AGENT_POLICIES_PERMISSION = i18n.translate( - 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission', + 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermissionUptime', { defaultMessage: 'You are not authorized to access Fleet. Fleet permissions are required to create new private locations.', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx index 11e2588e909b7..3bcaa51c48f77 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx @@ -146,7 +146,7 @@ export const INVALID_LABEL = i18n.translate('xpack.synthetics.monitorManagement. }); export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.cannotSaveIntegration', + 'xpack.synthetics.monitorManagement.cannotSaveIntegrationUptime', { defaultMessage: 'You are not authorized to update integrations. Integrations write permissions are required.', diff --git a/x-pack/plugins/synthetics/server/feature.ts b/x-pack/plugins/synthetics/server/feature.ts index 4026bbdb19fb9..908d6e6f1f9fa 100644 --- a/x-pack/plugins/synthetics/server/feature.ts +++ b/x-pack/plugins/synthetics/server/feature.ts @@ -57,7 +57,7 @@ export const uptimeFeature = { read: { app: ['uptime', 'kibana', 'synthetics'], catalogue: ['uptime'], - api: ['uptime-read', 'lists-read'], + api: ['uptime-read', 'lists-read', 'rac'], savedObject: { all: [], read: [ diff --git a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts index 058d28d046c8a..a737e80e08069 100644 --- a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts @@ -53,7 +53,6 @@ export async function queryMonitorStatus( const pageCount = Math.ceil(monitorQueryIds.length / idSize); let up = 0; let down = 0; - let pending = 0; const upConfigs: Record = {}; const downConfigs: Record = {}; const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap))); @@ -135,8 +134,6 @@ export async function queryMonitorStatus( 'getCurrentStatusOverview' + i ); - pending += idsToQuery.length - (result.aggregations?.id.buckets.length ?? 0); - result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => { const locationSummaries = location.buckets.map(({ status, key: locationName }) => { const ping = status.hits.hits[0]._source; @@ -188,8 +185,6 @@ export async function queryMonitorStatus( if (!monitorsWithoutData.get(monitorQueryId)?.length) { monitorsWithoutData.delete(monitorQueryId); } - } else { - pending += 1; } }); }); @@ -212,7 +207,7 @@ export async function queryMonitorStatus( return { up, down, - pending, + pending: Object.values(pendingConfigs).length, upConfigs, downConfigs, pendingConfigs, diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 6978a38b2be46..b394c53f20142 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -44,6 +44,9 @@ import { getHasIntegrationMonitorsRoute } from './fleet/get_has_integration_moni import { addSyntheticsParamsRoute } from './settings/add_param'; import { enableDefaultAlertingRoute } from './default_alerts/enable_default_alert'; import { getDefaultAlertingRoute } from './default_alerts/get_default_alert'; +import { addPrivateLocationRoute } from './settings/private_locations/add_private_location'; +import { deletePrivateLocationRoute } from './settings/private_locations/delete_private_location'; +import { getPrivateLocationsRoute } from './settings/private_locations/get_private_locations'; export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ addSyntheticsMonitorRoute, @@ -77,6 +80,9 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getDefaultAlertingRoute, updateDefaultAlertingRoute, createJourneyRoute, + addPrivateLocationRoute, + deletePrivateLocationRoute, + getPrivateLocationsRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts index 4679d555f6504..635b964c922ab 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts @@ -4,11 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { getUptimeESMockClient } from '../../legacy_uptime/lib/requests/test_helpers'; import { periodToMs } from './overview_status'; import { queryMonitorStatus } from '../../queries/query_monitor_status'; +import { RouteContext } from '../../legacy_uptime/routes'; +import { getStatus } from './overview_status'; import times from 'lodash/times'; +import * as monitorsFns from '../../saved_objects/synthetics_monitor/get_all_monitors'; +import { EncryptedSyntheticsMonitor } from '../../../common/runtime_types'; + +jest.mock('../../saved_objects/synthetics_monitor/get_all_monitors', () => ({ + ...jest.requireActual('../../saved_objects/synthetics_monitor/get_all_monitors'), + getAllMonitors: jest.fn(), +})); jest.mock('../common', () => ({ getMonitors: jest.fn().mockReturnValue({ @@ -34,6 +44,7 @@ jest.mock('../common', () => ({ }, ], }), + getMonitorFilters: () => '', })); jest.mock('../../legacy_uptime/lib/requests/get_snapshot_counts', () => ({ @@ -61,7 +72,7 @@ describe('current status route', () => { }); }); - describe('getStats', () => { + describe('queryMonitorStatus', () => { it('parses expected agg fields', async () => { const { esClient, uptimeEsClient } = getUptimeESMockClient(); esClient.search.mockResponseOnce( @@ -514,7 +525,7 @@ describe('current status route', () => { } ) ).toEqual({ - pending: 2, + pending: 4, down: 1, enabledMonitorQueryIds: ['id1', 'id2', 'project-monitor-id', 'id4'], up: 2, @@ -575,6 +586,239 @@ describe('current status route', () => { }); }); }); + + describe('getStatus', () => { + it.each([ + [['US Central QA'], 1], + [['North America - US Central'], 1], + [['North America - US Central', 'US Central QA'], 2], + [undefined, 2], + ])('handles disabled count when using location filters', async (locations, disabledCount) => { + jest.spyOn(monitorsFns, 'getAllMonitors').mockResolvedValue([ + { + type: 'synthetics-monitor', + id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + attributes: { + enabled: false, + schedule: { + number: '3', + unit: 'm', + }, + config_id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + locations: [ + { + color: 'default', + isServiceManaged: true, + label: 'US Central QA', + id: 'us_central_qa', + }, + { + isServiceManaged: true, + label: 'North America - US Central', + id: 'us_central', + }, + ], + origin: 'project', + id: 'a-test2-default', + }, + references: [], + migrationVersion: { + 'synthetics-monitor': '8.6.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2023-02-28T14:31:37.641Z', + created_at: '2023-02-28T14:31:37.641Z', + version: 'Wzg0MzkzLDVd', + namespaces: ['default'], + score: null, + sort: ['a', 3013], + } as unknown as SavedObjectsFindResult, + ]); + const { esClient, uptimeEsClient } = getUptimeESMockClient(); + esClient.search.mockResponseOnce( + getEsResponse([ + { + key: 'id1', + location: { + buckets: [ + { + key: 'Asia/Pacific - Japan', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:08:16.724Z', + monitor: { + status: 'up', + id: 'id1', + }, + summary: { + up: 1, + down: 0, + }, + config_id: 'id1', + observer: { + geo: { + name: 'Asia/Pacific - Japan', + }, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + key: 'id2', + location: { + buckets: [ + { + key: 'Asia/Pacific - Japan', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:09:16.724Z', + monitor: { + status: 'up', + id: 'id2', + }, + summary: { + up: 1, + down: 0, + }, + config_id: 'id2', + observer: { + geo: { + name: 'Asia/Pacific - Japan', + }, + }, + }, + }, + ], + }, + }, + }, + { + key: 'Europe - Germany', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:19:16.724Z', + monitor: { + status: 'down', + id: 'id2', + }, + summary: { + down: 1, + up: 0, + }, + config_id: 'id2', + observer: { + geo: { + name: 'Europe - Germany', + }, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + ]) + ); + expect( + await getStatus( + { + uptimeEsClient, + savedObjectsClient: savedObjectsClientMock.create(), + } as unknown as RouteContext, + { + locations, + } + ) + ).toEqual( + expect.objectContaining({ + disabledCount, + }) + ); + }); + + it.each([ + [['US Central QA'], 1], + [['North America - US Central'], 1], + [['North America - US Central', 'US Central QA'], 2], + [undefined, 2], + ])('handles pending count when using location filters', async (locations, pending) => { + jest.spyOn(monitorsFns, 'getAllMonitors').mockResolvedValue([ + { + type: 'synthetics-monitor', + id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + attributes: { + enabled: true, + schedule: { + number: '3', + unit: 'm', + }, + config_id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + locations: [ + { + color: 'default', + isServiceManaged: true, + label: 'US Central QA', + id: 'us_central_qa', + }, + { + isServiceManaged: true, + label: 'North America - US Central', + id: 'us_central', + }, + ], + origin: 'project', + id: 'a-test2-default', + }, + references: [], + migrationVersion: { + 'synthetics-monitor': '8.6.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2023-02-28T14:31:37.641Z', + created_at: '2023-02-28T14:31:37.641Z', + version: 'Wzg0MzkzLDVd', + namespaces: ['default'], + score: null, + sort: ['a', 3013], + } as unknown as SavedObjectsFindResult, + ]); + const { esClient, uptimeEsClient } = getUptimeESMockClient(); + esClient.search.mockResponseOnce(getEsResponse([])); + expect( + await getStatus( + { + uptimeEsClient, + savedObjectsClient: savedObjectsClientMock.create(), + } as unknown as RouteContext, + { + locations, + } + ) + ).toEqual( + expect.objectContaining({ + pending, + }) + ); + }); + }); }); function getEsResponse(buckets: any[]) { diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts index 750727d85f7ce..11ae9ff793e21 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts @@ -76,7 +76,13 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue disabledMonitorsCount, projectMonitorsCount, monitorQueryIdToConfigIdMap, - } = await processMonitors(allMonitors, server, savedObjectsClient, syntheticsMonitorClient); + } = await processMonitors( + allMonitors, + server, + savedObjectsClient, + syntheticsMonitorClient, + queryLocations + ); // Account for locations filter const queryLocationsArray = diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts new file mode 100644 index 0000000000000..9edd4bf53f81a --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { getAllPrivateLocations } from './get_private_locations'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { PrivateLocation } from '../../../../common/runtime_types'; + +export const PrivateLocationSchema = schema.object({ + label: schema.string(), + id: schema.string(), + agentPolicyId: schema.string(), + concurrentMonitors: schema.number(), + tags: schema.maybe(schema.arrayOf(schema.string())), + geo: schema.maybe( + schema.object({ + lat: schema.oneOf([schema.number(), schema.string()]), + lon: schema.oneOf([schema.number(), schema.string()]), + }) + ), +}); + +export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'POST', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, + validate: { + body: PrivateLocationSchema, + }, + writeAccess: true, + handler: async ({ request, server, savedObjectsClient }): Promise => { + const location = request.body as PrivateLocation; + + const { locations } = await getAllPrivateLocations(savedObjectsClient); + const existingLocations = locations.filter((loc) => loc.id !== location.agentPolicyId); + + const result = await savedObjectsClient.create( + privateLocationsSavedObjectName, + { locations: [...existingLocations, location] }, + { + id: privateLocationsSavedObjectId, + overwrite: true, + } + ); + + return result.attributes; + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts new file mode 100644 index 0000000000000..7360adfbc0b60 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts @@ -0,0 +1,43 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { getAllPrivateLocations } from './get_private_locations'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; + +export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'DELETE', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + '/{locationId}', + validate: { + params: schema.object({ + locationId: schema.string({ minLength: 1, maxLength: 1024 }), + }), + }, + writeAccess: true, + handler: async ({ savedObjectsClient, request, server }): Promise => { + const { locationId } = request.params as { locationId: string }; + + const { locations } = await getAllPrivateLocations(savedObjectsClient); + const remainingLocations = locations.filter((loc) => loc.id !== locationId); + + const result = await savedObjectsClient.create( + privateLocationsSavedObjectName, + { locations: remainingLocations }, + { + id: privateLocationsSavedObjectId, + overwrite: true, + } + ); + + return result.attributes; + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.ts new file mode 100644 index 0000000000000..82b1f4f4e0e8a --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.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 { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SyntheticsPrivateLocations } from '../../../../common/runtime_types'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; + +export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, + validate: {}, + handler: async ({ savedObjectsClient }): Promise => { + return await getAllPrivateLocations(savedObjectsClient); + }, +}); + +export const getAllPrivateLocations = async ( + savedObjectsClient: SavedObjectsClientContract +): Promise => { + try { + const obj = await savedObjectsClient.get( + privateLocationsSavedObjectName, + privateLocationsSavedObjectId + ); + return obj?.attributes ?? { locations: [] }; + } catch (getErr) { + return { locations: [] }; + } +}; diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index d134705093937..e0f404ebef875 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -11,6 +11,7 @@ import { SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; import pMap from 'p-map'; +import { intersection } from 'lodash'; import { periodToMs } from '../../routes/overview_status/overview_status'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; @@ -64,7 +65,8 @@ export const processMonitors = async ( allMonitors: Array>, server: UptimeServerSetup, soClient: SavedObjectsClientContract, - syntheticsMonitorClient: SyntheticsMonitorClient + syntheticsMonitorClient: SyntheticsMonitorClient, + queryLocations?: string[] | string ) => { /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. @@ -72,7 +74,6 @@ export const processMonitors = async ( * Track max period to make sure the snapshot query should reach back far enough to catch * latest ping for all enabled monitors. */ - const enabledMonitorQueryIds: string[] = []; let disabledCount = 0; let disabledMonitorsCount = 0; @@ -109,7 +110,13 @@ export const processMonitors = async ( monitorQueryIdToConfigIdMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = attrs[ConfigKey.CONFIG_ID]; if (attrs[ConfigKey.ENABLED] === false) { - disabledCount += attrs[ConfigKey.LOCATIONS].length; + const monitorLocations = attrs[ConfigKey.LOCATIONS].map((location) => location.label); + const queriedLocations = Array.isArray(queryLocations) ? queryLocations : [queryLocations]; + const intersectingLocations = intersection( + monitorLocations, + queryLocations ? queriedLocations : monitorLocations + ); + disabledCount += intersectingLocations.length; disabledMonitorsCount += 1; } else { const missingLabels = new Set(); @@ -133,7 +140,10 @@ export const processMonitors = async ( getLocationLabel(locationId) ); - monitorLocationMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = [...monLocs, ...locLabels]; + const monitorLocations = [...monLocs, ...locLabels]; + monitorLocationMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = queryLocations + ? intersection(monitorLocations, queryLocations) + : monitorLocations; listOfLocationsSet = new Set([...listOfLocationsSet, ...monLocs, ...locLabels]); maxPeriod = Math.max(maxPeriod, periodToMs(attrs[ConfigKey.SCHEDULE])); 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 5a94f1361380c..99e466ebbb357 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5502,18 +5502,6 @@ }, "maps": { "properties": { - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoPointFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoShapeFieldCount": { - "type": "long" - }, - "geoShapeAggLayersCount": { - "type": "long" - }, "mapsTotalCount": { "type": "long" }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 516eef2d0157e..fbd6695963d91 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2477,7 +2477,6 @@ "exceptionList-components.exception_list_header_edit_modal_save_button": "Enregistrer", "exceptionList-components.exception_list_header_export_action": "Exporter la liste d'exceptions", "exceptionList-components.exception_list_header_list_id": "ID de liste", - "exceptionList-components.exception_list_header_manage_rules_button": "Gérer les règles", "exceptionList-components.exception_list_header_name": "Ajouter un nom", "exceptionList-components.exception_list_header_Name_textbox": "Nom", "exceptionList-components.exceptions.exceptionItem.card.conditions.and": "AND", @@ -3880,7 +3879,6 @@ "indexPatternEditor.typeSelect.standardDescription": "Effectuer des agrégations complètes à partir de n'importe quelles données", "indexPatternEditor.typeSelect.standardTitle": "Vue de données standard", "indexPatternEditor.validations.noSingleAstriskPattern": "Un seul astérisque \"*\" n’est pas un modèle d'indexation autorisé", - "indexPatternEditor.validations.titleHelpText": "Entrez un modèle d'indexation qui correspond à une ou plusieurs sources de données. Utilisez un astérisque (*) pour faire correspondre plusieurs caractères. Les espaces et les caractères , /, ?, \", <, >, | ne sont pas autorisés.", "indexPatternEditor.validations.titleIsRequiredErrorMessage": "Un modèle d'indexation est requis.", "indexPatternFieldEditor.date.momentLabel": "Modèle de format Moment.js (par défaut : {defaultPattern})", "indexPatternFieldEditor.defaultErrorMessage": "Une erreur s'est produite lors de l'utilisation de cette configuration de format : {message}.", @@ -10586,8 +10584,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "Cliquer sur {addAuthenticationButtonLabel} afin de fournir les informations d'identification nécessaires pour indexer le contenu protégé", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{crawlType} indexation sur {domainCount, plural, one {# domaine} other {# domaines}}", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "Inclure les plans de site découverts dans {robotsDotTxt}", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "Créez une règle d'indexation pour inclure ou exclure les pages dont l'URL correspond à la règle. Les règles sont exécutées dans l'ordre séquentiel, et chaque URL est évaluée en fonction de la première correspondance. {link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Le robot d'indexation n'indexe que les pages uniques. Choisissez les champs que le robot d'indexation doit utiliser lorsqu'il recherche les pages en double. Désélectionnez tous les champs de schéma pour autoriser les documents en double dans ce domaine. {documentationLink}.", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "Supprimer le domaine {domainUrl} de votre robot d'indexation. Cela supprimera également tous les points d'entrée et toutes les règles d'indexation que vous avez configurés. Tous les documents associés à ce domaine seront supprimés lors de la prochaine indexation. {thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link} pour spécifier un point d'entrée pour le robot d'indexation", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "Les nœuds Enterprise Search fonctionnent-ils dans votre déploiement cloud ? {deploymentSettingsLink}", @@ -26778,7 +26774,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "Masquer ({comments}) {comments, plural, =1 {commentaire} other {commentaires}}", "xpack.securitySolution.exceptions.list.deleted_successfully": "{listName} supprimée avec succès", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "\"{itemName}\" supprimé avec succès.", - "xpack.securitySolution.exceptions.list.exported_successfully": "{listName} exporté avec succès", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "Voulez-vous vraiment SUPPRIMER la liste d'exceptions nommée {listName} ?", "xpack.securitySolution.exceptions.referenceModalDescription": "Cette liste d'exceptions est associée à ({referenceCount}) {referenceCount, plural, =1 {règle} other {règles}}. Le retrait de cette liste d'exceptions supprimera également sa référence des règles associées.", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "Liste d'exceptions - {listId} - supprimée avec succès.", @@ -28634,7 +28629,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "Une erreur s'est produite lors de la suppression de la liste d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "Erreur lors de la récupération des listes d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "Erreur d'exportation de la liste d'exceptions", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "Réussite de l'exportation de la liste d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "ID de liste", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "Nom", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "Actualiser", @@ -29584,7 +29578,6 @@ "xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "Une erreur s'est produite lors du chargement des éléments d'exception. Contactez votre administrateur pour obtenir de l'aide.", "xpack.securitySolution.exceptions.list.manage_rules_cancel": "Annuler", "xpack.securitySolution.exceptions.list.manage_rules_description": "Associez des règles à cette liste d'exceptions ou dissociez des règles de cette liste.", - "xpack.securitySolution.exceptions.list.manage_rules_header": "Gérer les règles", "xpack.securitySolution.exceptions.list.manage_rules_save": "Enregistrer", "xpack.securitySolution.exceptions.list.utility.title": "exceptions de règle", "xpack.securitySolution.exceptions.manageExceptions.createItemButton": "Créer un élément d'exception", @@ -33660,7 +33653,6 @@ "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "Clés d'API", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "Génération d’une clé d’API", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "Utilisez une clé d’API pour transmettre des moniteurs à distance à partir d'un pipeline CLI ou CD. Pour générer une clé d’API, vous devez disposer des autorisations de gérer les clés d’API et d’un accès en écriture à Uptime. Veuillez contacter votre administrateur.", - "xpack.synthetics.monitorManagement.gettingStarted.label": "Lancez-vous avec Synthetic Monitoring", "xpack.synthetics.monitorManagement.heading": "Gestion des moniteurs", "xpack.synthetics.monitorManagement.hostFieldLabel": "Hôte", "xpack.synthetics.monitorManagement.inProgress": "EN COURS", @@ -33708,9 +33700,7 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "Statut", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "Impossible de synchroniser les moniteurs avec le service Synthetics", "xpack.synthetics.monitorManagement.nameRequired": "Le nom de l’emplacement est requis", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Vous n'êtes pas autorisé à accéder à Fleet. Des autorisations Fleet sont nécessaires pour créer de nouveaux emplacements privés.", "xpack.synthetics.monitorManagement.needPermissions": "Permissions requises", - "xpack.synthetics.monitorManagement.new.label": "Nouveauté", "xpack.synthetics.monitorManagement.noLabel": "Annuler", "xpack.synthetics.monitorManagement.overviewTab.title": "Aperçu", "xpack.synthetics.monitorManagement.pageHeader.title": "Gestion des moniteurs", @@ -33745,8 +33735,6 @@ "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "Activez la Gestion des moniteurs pour créer des moniteurs légers et basés sur un navigateur réel à partir d'emplacements du monde entier.", "xpack.synthetics.monitorManagement.techPreviewLabel": "Préversion technique", "xpack.synthetics.monitorManagement.testResult": "Résultat du test", - "xpack.synthetics.monitorManagement.try.dismiss": "Rejeter", - "xpack.synthetics.monitorManagement.try.label": "Essayer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.updateMonitorLabel": "Mettre à jour le moniteur", "xpack.synthetics.monitorManagement.urlFieldLabel": "Url", "xpack.synthetics.monitorManagement.urlRequiredLabel": "L'URL est requise", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c81197b7eba19..8ed17cfebea7a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2475,7 +2475,6 @@ "exceptionList-components.exception_list_header_edit_modal_save_button": "保存", "exceptionList-components.exception_list_header_export_action": "例外リストのエクスポート", "exceptionList-components.exception_list_header_list_id": "リスト ID", - "exceptionList-components.exception_list_header_manage_rules_button": "ルールの管理", "exceptionList-components.exception_list_header_name": "名前を追加", "exceptionList-components.exception_list_header_Name_textbox": "名前", "exceptionList-components.exceptions.exceptionItem.card.conditions.and": "AND", @@ -3877,7 +3876,6 @@ "indexPatternEditor.typeSelect.standardDescription": "すべてのデータに完全アグリゲーションを実行", "indexPatternEditor.typeSelect.standardTitle": "標準データビュー", "indexPatternEditor.validations.noSingleAstriskPattern": "単一の「*」は許可されたパターンではありません", - "indexPatternEditor.validations.titleHelpText": "1つ以上のデータソースと一致するインデックスパターンを入力します。複数の文字の一致にアスタリスク(*)を使用します。ペースと / ? , \" < > | 文字は使用できません。", "indexPatternEditor.validations.titleIsRequiredErrorMessage": "インデックスパターンが必要です。", "indexPatternFieldEditor.date.momentLabel": "Moment.jsのフォーマットパターン(デフォルト:{defaultPattern})", "indexPatternFieldEditor.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました:{message}", @@ -10573,8 +10571,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "{addAuthenticationButtonLabel}をクリックすると、保護されたコンテンツのクローリングに必要な資格情報を提供します", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{domainCount, plural, other {# 件のドメイン}}で{crawlType}クロール", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "{robotsDotTxt}で検出されたサイトマップを含める", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "URLがルールと一致するページを含めるか除外するためのクロールルールを作成します。ルールは連続で実行されます。各URLは最初の一致に従って評価されます。{link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Webクローラーは一意のページにのみインデックスします。重複するページを検討するときにクローラーが使用するフィールドを選択します。すべてのスキーマフィールドを選択解除して、このドメインで重複するドキュメントを許可します。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "ドメイン{domainUrl}をクローラーから削除します。これにより、設定したすべてのエントリポイントとクロールルールも削除されます。このドメインに関連するすべてのドキュメントは、次回のクロールで削除されます。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "クローラーのエントリポイントを指定するには、{link}してください", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "クラウドデプロイのエンタープライズ サーチノードが実行中ですか?{deploymentSettingsLink}", @@ -26751,7 +26747,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "({comments}){comments, plural, other {件のコメント}}を非表示", "xpack.securitySolution.exceptions.list.deleted_successfully": "{listName}が正常に削除されました", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "\"{itemName}\"が正常に削除されました。", - "xpack.securitySolution.exceptions.list.exported_successfully": "{listName}が正常にエクスポートされました", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "名前{listName}の例外リストを削除しますか?", "xpack.securitySolution.exceptions.referenceModalDescription": "この例外リストは、({referenceCount}) {referenceCount, plural, other {個のルール}}に関連付けられています。この例外リストを削除すると、関連付けられたルールからの参照も削除されます。", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "例外リスト{listId}が正常に削除されました。", @@ -28606,7 +28601,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "例外リストの削除中にエラーが発生しました", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "例外リストの取得エラー", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "例外リストエクスポートエラー", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "例外リストエクスポート成功", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "リスト ID", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "名前", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "更新", @@ -29553,7 +29547,6 @@ "xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "例外アイテムの読み込みエラーが発生しました。ヘルプについては、管理者にお問い合わせください。", "xpack.securitySolution.exceptions.list.manage_rules_cancel": "キャンセル", "xpack.securitySolution.exceptions.list.manage_rules_description": "ルールをこの例外リストに関連付けたり、関連付けを解除したりします。", - "xpack.securitySolution.exceptions.list.manage_rules_header": "ルールの管理", "xpack.securitySolution.exceptions.list.manage_rules_save": "保存", "xpack.securitySolution.exceptions.list.utility.title": "ルール例外", "xpack.securitySolution.exceptions.manageExceptions.createItemButton": "例外アイテムの作成", @@ -33631,7 +33624,6 @@ "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "API キー", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "APIキーを生成しています", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "APIキーを使用して、CLIまたはCDパイプラインからリモートでモニターをプッシュします。APIキーを生成するには、APIキーを管理する権限とアップタイム書き込み権限が必要です。管理者にお問い合わせください。", - "xpack.synthetics.monitorManagement.gettingStarted.label": "Synthetic Monitoringの基本", "xpack.synthetics.monitorManagement.heading": "モニター管理", "xpack.synthetics.monitorManagement.hostFieldLabel": "ホスト", "xpack.synthetics.monitorManagement.inProgress": "進行中", @@ -33679,9 +33671,7 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "ステータス", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "モニターをSyntheticsサービスと同期できませんでした", "xpack.synthetics.monitorManagement.nameRequired": "場所名は必須です", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Fleet へのアクセスが許可されていません。新しい非公開の場所を作成するには、Fleet権限が必要です。", "xpack.synthetics.monitorManagement.needPermissions": "権限が必要です", - "xpack.synthetics.monitorManagement.new.label": "新規", "xpack.synthetics.monitorManagement.noLabel": "キャンセル", "xpack.synthetics.monitorManagement.overviewTab.title": "概要", "xpack.synthetics.monitorManagement.pageHeader.title": "モニター管理", @@ -33716,8 +33706,6 @@ "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "モニター管理を有効にすると、世界中の場所から軽量でリアルなブラウザーモニターを作成できます。", "xpack.synthetics.monitorManagement.techPreviewLabel": "テクニカルプレビュー", "xpack.synthetics.monitorManagement.testResult": "テスト結果", - "xpack.synthetics.monitorManagement.try.dismiss": "閉じる", - "xpack.synthetics.monitorManagement.try.label": "モニター管理を試す", "xpack.synthetics.monitorManagement.updateMonitorLabel": "モニターの更新", "xpack.synthetics.monitorManagement.urlFieldLabel": "Url", "xpack.synthetics.monitorManagement.urlRequiredLabel": "URLが必要です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 53f914a9e72e3..1fcfd93fc4142 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2479,7 +2479,6 @@ "exceptionList-components.exception_list_header_edit_modal_save_button": "保存", "exceptionList-components.exception_list_header_export_action": "导出例外列表", "exceptionList-components.exception_list_header_list_id": "列表 ID", - "exceptionList-components.exception_list_header_manage_rules_button": "管理规则", "exceptionList-components.exception_list_header_name": "添加名称", "exceptionList-components.exception_list_header_Name_textbox": "名称", "exceptionList-components.exceptions.exceptionItem.card.conditions.and": "且", @@ -3882,7 +3881,6 @@ "indexPatternEditor.typeSelect.standardDescription": "对任何数据执行完全聚合", "indexPatternEditor.typeSelect.standardTitle": "标准数据视图", "indexPatternEditor.validations.noSingleAstriskPattern": "不允许以单个“*”作为索引模式", - "indexPatternEditor.validations.titleHelpText": "输入与一个或多个数据源匹配的索引模式。使用星号 (*) 匹配多个字符。不允许使用空格和字符 /、?、\"、<、>、|。", "indexPatternEditor.validations.titleIsRequiredErrorMessage": "“索引模式”必填。", "indexPatternFieldEditor.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern})", "indexPatternFieldEditor.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}", @@ -10590,8 +10588,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "单击{addAuthenticationButtonLabel}以提供爬网受保护内容所需的凭据", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "在 {domainCount, plural, other {# 个域}}上进行 {crawlType} 爬网", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "包括在 {robotsDotTxt} 中发现的站点地图", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "创建爬网规则以包括或排除 URL 匹配规则的页面。规则按顺序运行,每个 URL 根据第一个匹配进行评估。{link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "网络爬虫仅索引唯一的页面。选择网络爬虫在考虑哪些网页重复时应使用的字段。取消选择所有架构字段以在此域上允许重复的文档。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "从网络爬虫中移除域 {domainUrl}。这还会删除您已设置的所有入口点和爬网规则。将在下次爬网时移除与此域相关的任何文档。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link}以指定网络爬虫的入口点", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "您的云部署是否正在运行 Enterprise Search 节点?{deploymentSettingsLink}", @@ -26786,7 +26782,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "隐藏 ({comments}) 个{comments, plural, other {注释}}", "xpack.securitySolution.exceptions.list.deleted_successfully": "已成功删除 {listName}", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "已成功删除“{itemName}”。", - "xpack.securitySolution.exceptions.list.exported_successfully": "已成功导出 {listName}", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "是否确定要删除名为 {listName} 的例外列表?", "xpack.securitySolution.exceptions.referenceModalDescription": "此例外列表与 ({referenceCount}) 个{referenceCount, plural, other {规则}}关联。移除此例外列表还将会删除其对关联规则的引用。", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "例外列表 - {listId} - 已成功删除。", @@ -28640,7 +28635,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "删除例外列表时发生错误", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "提取例外列表时出错", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "例外列表导出错误", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "例外列表导出成功", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "列表 ID", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "名称", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "刷新", @@ -29587,7 +29581,6 @@ "xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "加载例外项时出现错误。请联系您的管理员寻求帮助。", "xpack.securitySolution.exceptions.list.manage_rules_cancel": "取消", "xpack.securitySolution.exceptions.list.manage_rules_description": "将规则链接到此例外列表或取消链接。", - "xpack.securitySolution.exceptions.list.manage_rules_header": "管理规则", "xpack.securitySolution.exceptions.list.manage_rules_save": "保存", "xpack.securitySolution.exceptions.list.utility.title": "规则例外", "xpack.securitySolution.exceptions.manageExceptions.createItemButton": "创建例外项", @@ -33666,7 +33659,6 @@ "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "API 密钥", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "正在生成 API 密钥", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "使用 API 密钥从 CLI 或 CD 管道远程推送监测。要生成 API 密钥,您必须有权管理 API 密钥并具有 Uptime 写入权限。请联系您的管理员。", - "xpack.synthetics.monitorManagement.gettingStarted.label": "开始使用 Synthetic 监测", "xpack.synthetics.monitorManagement.heading": "监测管理", "xpack.synthetics.monitorManagement.hostFieldLabel": "主机", "xpack.synthetics.monitorManagement.inProgress": "进行中", @@ -33714,9 +33706,7 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "状态", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "监测无法与 Synthetics 服务同步", "xpack.synthetics.monitorManagement.nameRequired": "“位置名称”必填", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "您无权访问 Fleet。需要 Fleet 权限才能创建新的专用位置。", "xpack.synthetics.monitorManagement.needPermissions": "需要权限", - "xpack.synthetics.monitorManagement.new.label": "新建", "xpack.synthetics.monitorManagement.noLabel": "取消", "xpack.synthetics.monitorManagement.overviewTab.title": "概览", "xpack.synthetics.monitorManagement.pageHeader.title": "监测管理", @@ -33751,8 +33741,6 @@ "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "启用监测管理以在全球各个地点创建轻量级、真正的浏览器监测。", "xpack.synthetics.monitorManagement.techPreviewLabel": "技术预览", "xpack.synthetics.monitorManagement.testResult": "测试结果", - "xpack.synthetics.monitorManagement.try.dismiss": "关闭", - "xpack.synthetics.monitorManagement.try.label": "尝试监测管理", "xpack.synthetics.monitorManagement.updateMonitorLabel": "更新监测", "xpack.synthetics.monitorManagement.urlFieldLabel": "URL", "xpack.synthetics.monitorManagement.urlRequiredLabel": "“URL”必填", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx new file mode 100644 index 0000000000000..2c1b10fca0c6c --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { IToasts } from '@kbn/core/public'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { UpdateApiKeyModalConfirmation } from './update_api_key_modal_confirmation'; +import { useKibana } from '../../common/lib/kibana'; + +const Providers = ({ children }: { children: any }) => ( + {children} +); + +const renderWithProviders = (ui: any) => { + return render(ui, { wrapper: Providers }); +}; + +jest.mock('../../common/lib/kibana'); +const useKibanaMock = useKibana as jest.Mocked; + +describe('Update Api Key', () => { + const onCancel = jest.fn(); + const apiUpdateApiKeyCall = jest.fn(); + const setIsLoadingState = jest.fn(); + const onUpdated = jest.fn(); + const onSearchPopulate = jest.fn(); + + const addSuccess = jest.fn(); + const addError = jest.fn(); + + beforeAll(() => { + useKibanaMock().services.notifications.toasts = { + addSuccess, + addError, + } as unknown as IToasts; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Render modal updates Api Key', async () => { + renderWithProviders( + + ); + + expect( + await screen.findByText('You will not be able to recover the old API key') + ).toBeInTheDocument(); + }); + + it('Cancel modal updates Api Key', async () => { + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Cancel')); + expect(onCancel).toHaveBeenCalled(); + }); + + it('Update an Api Key', async () => { + apiUpdateApiKeyCall.mockResolvedValue({}); + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Update')); + expect(setIsLoadingState).toBeCalledTimes(1); + expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] })); + await waitFor(() => { + expect(setIsLoadingState).toBeCalledTimes(2); + expect(onUpdated).toHaveBeenCalled(); + }); + }); + + it('Failed to update an Api Key', async () => { + apiUpdateApiKeyCall.mockRejectedValue(500); + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Update')); + expect(setIsLoadingState).toBeCalledTimes(1); + expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] })); + await waitFor(() => { + expect(setIsLoadingState).toBeCalledTimes(2); + expect(addError).toHaveBeenCalled(); + expect(addError.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + 500, + Object { + "title": "Failed to update the API key", + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index f70437b33c451..2775b200cb6c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -146,10 +146,7 @@ const renderWithProviders = (ui: any) => { return render(ui, { wrapper: AllTheProviders }); }; -// FLAKY: https://github.com/elastic/kibana/issues/134922 -// FLAKY: https://github.com/elastic/kibana/issues/134923 - -describe.skip('Update Api Key', () => { +describe('Update Api Key', () => { const addSuccess = jest.fn(); const addError = jest.fn(); @@ -177,7 +174,7 @@ describe.skip('Update Api Key', () => { cleanup(); }); - it('Updates the Api Key successfully', async () => { + it('Have the option to update API key', async () => { bulkUpdateAPIKey.mockResolvedValueOnce({ errors: [], total: 1, rules: [], skipped: [] }); renderWithProviders(); @@ -186,45 +183,7 @@ describe.skip('Update Api Key', () => { fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - fireEvent.click(await screen.findByText('Update API key')); - expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Cancel')); - expect( - screen.queryByText('You will not be able to recover the old API key') - ).not.toBeInTheDocument(); - - fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); - expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update API key')); - - fireEvent.click(await screen.findByTestId('confirmModalConfirmButton')); - await waitFor(() => expect(addSuccess).toHaveBeenCalledWith('Updated API key for 1 rule.')); - expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] })); - expect(screen.queryByText("You can't recover the old API key")).not.toBeInTheDocument(); - }); - - it('Update API key fails', async () => { - bulkUpdateAPIKey.mockRejectedValueOnce(500); - renderWithProviders(); - - expect(await screen.findByText('test rule ok')).toBeInTheDocument(); - - fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); - expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update API key')); - expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update')); - await waitFor(() => - expect(addError).toHaveBeenCalledWith(500, { title: 'Failed to update the API key' }) - ); - expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] })); - expect( - screen.queryByText('You will not be able to recover the old API key') - ).not.toBeInTheDocument(); + expect(screen.queryByText('Update API key')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index e20fdd1c1a0b7..0819f7541d1cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -185,6 +185,7 @@ export const GroupByExpression = ({ 0} error={errors.termSize}> { + it('should generate expected events for flapping alerts that settle on active', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -642,7 +642,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, true, true, true, true]; + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); const pattern = { instance, }; @@ -664,6 +667,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], notify_when: RuleNotifyWhen.CHANGE, }) @@ -685,10 +693,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // make sure the counts of the # of events per type are as expected ['execute-start', { gte: 6 }], ['execute', { gte: 6 }], - ['execute-action', { equal: 1 }], - ['new-instance', { equal: 1 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 1 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -700,19 +708,24 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - const result = [false, true, true, true, false, false, false, false]; + const result = [false, false, false, false, false].concat( + new Array(9).fill(true), + false, + false, + false + ); expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that are mainly recovered', async () => { + it('should generate expected events for flapping alerts settle on recovered', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -727,7 +740,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, false, false, false, true]; + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); const pattern = { instance, }; @@ -749,6 +764,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], notify_when: RuleNotifyWhen.CHANGE, }) @@ -770,10 +790,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // make sure the counts of the # of events per type are as expected ['execute-start', { gte: 6 }], ['execute', { gte: 6 }], - ['execute-action', { equal: 2 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 2 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 3 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -785,18 +805,20 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - expect(flapping).to.eql([false, true, true, true, true, true, true, true]); + expect(flapping).to.eql( + [false, false, false, false, false].concat(new Array(8).fill(true)) + ); }); - it('should generate expected events for flapping alerts that are mainly active with notifyWhen not set to "on status change"', async () => { + it('should generate expected events for flapping alerts over a period of time longer than the look back', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 5, + status_change_threshold: 5, }) .expect(200); const { body: createdAction } = await supertest @@ -811,7 +833,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, true, true, true, true]; + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); const pattern = { instance, }; @@ -833,7 +858,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], + notify_when: RuleNotifyWhen.CHANGE, }) ); @@ -851,12 +882,110 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 6 }], - ['execute', { gte: 6 }], - ['execute-action', { equal: 6 }], - ['new-instance', { equal: 1 }], + ['execute-start', { gte: 8 }], + ['execute', { gte: 8 }], + ['execute-action', { equal: 8 }], + ['new-instance', { equal: 4 }], + ['active-instance', { gte: 4 }], + ['recovered-instance', { equal: 4 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + const result = [false, false, false, false, false, false, false].concat( + new Array(6).fill(true), + false, + false, + false, + false + ); + expect(flapping).to.eql(result); + }); + + it('should generate expected events for flapping alerts that settle on active where notifyWhen is not set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 15 }], + ['execute', { gte: 15 }], + ['execute-action', { equal: 15 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 1 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -868,19 +997,24 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - const result = [false, true, true, false, false, false, false]; + const result = [false, false, false, false, false].concat( + new Array(7).fill(true), + false, + false, + false + ); expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that are mainly recovered with notifyWhen not set to "on status change"', async () => { + it('should generate expected events for flapping alerts that settle on recovered where notifyWhen is not set to "on status change"', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -895,7 +1029,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, false, false, false, true]; + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); const pattern = { instance, }; @@ -917,6 +1053,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], }) ); @@ -935,12 +1076,12 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['execute-action', { equal: 3 }], - ['new-instance', { equal: 2 }], + ['execute-start', { gte: 8 }], + ['execute', { gte: 8 }], + ['execute-action', { equal: 8 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 3 }], - ['recovered-instance', { equal: 2 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -952,7 +1093,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - expect(flapping).to.eql([false, true, true, true, true]); + expect(flapping).to.eql([false, false, false, false, false, true, true, true]); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts index 97ef276fef930..cc5f3108ddd54 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts @@ -132,7 +132,7 @@ export default function eventLogAlertTests({ getService }: FtrProviderContext) { break; } } - expect(flapping).to.eql(new Array(instanceEvents.length - 1).fill(false).concat([true])); + expect(flapping).to.eql(new Array(instanceEvents.length).fill(false)); }); }); } diff --git a/x-pack/test/api_integration/apis/cases/common/users.ts b/x-pack/test/api_integration/apis/cases/common/users.ts index 29c14c35a5b76..f37b13b98f31a 100644 --- a/x-pack/test/api_integration/apis/cases/common/users.ts +++ b/x-pack/test/api_integration/apis/cases/common/users.ts @@ -4,12 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.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 { User } from '../../../../cases_api_integration/common/lib/authentication/types'; import { diff --git a/x-pack/test/api_integration/apis/cases/files.ts b/x-pack/test/api_integration/apis/cases/files.ts new file mode 100644 index 0000000000000..95ad400459f02 --- /dev/null +++ b/x-pack/test/api_integration/apis/cases/files.ts @@ -0,0 +1,365 @@ +/* + * 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 { APP_ID as CASES_APP_ID } from '@kbn/cases-plugin/common/constants'; +import { APP_ID as SECURITY_SOLUTION_APP_ID } from '@kbn/security-solution-plugin/common/constants'; +import { observabilityFeatureId as OBSERVABILITY_APP_ID } from '@kbn/observability-plugin/common'; +import { Owner } from '@kbn/cases-plugin/common/constants/types'; +import { BaseFilesClient } from '@kbn/shared-ux-file-types'; +import { User } from '../../../cases_api_integration/common/lib/authentication/types'; +import { + createFile, + deleteFiles, + uploadFile, + downloadFile, + createAndUploadFile, + listFiles, + getFileById, + deleteAllFiles, +} from '../../../cases_api_integration/common/lib/api'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { + casesAllUser, + casesNoDeleteUser, + casesReadUser, + obsCasesAllUser, + obsCasesNoDeleteUser, + obsCasesReadUser, + secAllCasesNoDeleteUser, + secAllUser, + secReadCasesReadUser, +} from './common/users'; + +interface TestScenario { + user: User; + owner: Owner; +} + +export default ({ getService }: FtrProviderContext): void => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const supertest = getService('supertest'); + + describe('files', () => { + describe('failure requests', () => { + const createFileFailure = async (scenario: TestScenario) => { + await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + expectedHttpCode: 403, + }); + }; + + const deleteFileFailure = async (scenario: TestScenario) => { + await deleteFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + files: [{ kind: scenario.owner, id: 'abc' }], + expectedHttpCode: 403, + }); + }; + + const uploadFileFailure = async (scenario: TestScenario) => { + await uploadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + data: 'abc', + kind: scenario.owner, + mimeType: 'image/png', + fileId: '123', + expectedHttpCode: 403, + }); + }; + + const listFilesFailure = async (scenario: TestScenario) => { + await listFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + }, + expectedHttpCode: 403, + }); + }; + + const downloadFileFailure = async (scenario: TestScenario) => { + await downloadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + fileId: 'abc', + fileName: '123', + mimeType: 'image/png', + kind: scenario.owner, + expectedHttpCode: 401, + }); + }; + + const getFileByIdFailure = async (scenario: TestScenario) => { + await getFileById({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + id: 'abc', + kind: scenario.owner, + expectedHttpCode: 403, + }); + }; + + describe('user not authorized for a delete operation', () => { + const testScenarios: TestScenario[] = [ + { user: secAllCasesNoDeleteUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesNoDeleteUser, owner: CASES_APP_ID }, + { user: obsCasesNoDeleteUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + } + }); + + describe('user not authorized for write operations', () => { + const testScenarios: TestScenario[] = [ + { user: secReadCasesReadUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesReadUser, owner: CASES_APP_ID }, + { user: obsCasesReadUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + it('should fail to create a file', async () => { + await createFileFailure(scenario); + }); + + it('should fail to upload a file', async () => { + await uploadFileFailure(scenario); + }); + + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + } + }); + + describe('user not authorized for file kind', () => { + const testScenarios: TestScenario[] = [ + { user: secAllUser, owner: CASES_APP_ID }, + { + user: casesAllUser, + owner: SECURITY_SOLUTION_APP_ID, + }, + { + user: obsCasesAllUser, + owner: CASES_APP_ID, + }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + it('should fail to create a file', async () => { + await createFileFailure(scenario); + }); + + it('should fail to upload a file', async () => { + await uploadFileFailure(scenario); + }); + + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + + it('should fail to list files', async () => { + await listFilesFailure(scenario); + }); + + it('should fail to download a file', async () => { + await downloadFileFailure(scenario); + }); + + it('should fail to get a file by its id', async () => { + await getFileByIdFailure(scenario); + }); + }); + } + }); + }); + + describe('successful requests', () => { + describe('users with read privileges', () => { + const testScenarios: TestScenario[] = [ + { user: secReadCasesReadUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesReadUser, owner: CASES_APP_ID }, + { user: obsCasesReadUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + let createdFile: Awaited>; + + beforeEach(async () => { + const { create } = await createAndUploadFile({ + supertest, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + }); + createdFile = create; + }); + + afterEach(async () => { + await deleteAllFiles({ + supertest, + kind: scenario.owner, + }); + }); + + it('should list files', async () => { + const files = await listFiles({ + supertest: supertestWithoutAuth, + params: { kind: scenario.owner }, + auth: { user: scenario.user, space: null }, + }); + + expect(files.total).to.be(1); + expect(files.files[0].name).to.be(createdFile.file.name); + }); + + it('should get a file by its id', async () => { + const file = await getFileById({ + supertest: supertestWithoutAuth, + id: createdFile.file.id, + kind: scenario.owner, + auth: { user: scenario.user, space: null }, + }); + + expect(file.file.name).to.be(createdFile.file.name); + }); + }); + } + }); + + describe('users with all privileges', () => { + const testScenarios: TestScenario[] = [ + { user: secAllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesAllUser, owner: CASES_APP_ID }, + { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + it('should create and delete a file', async () => { + const createResult = await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + }); + + await deleteFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + files: [{ kind: scenario.owner, id: createResult.file.id }], + }); + }); + + describe('delete created file after test', () => { + afterEach(async () => { + await deleteAllFiles({ + supertest, + kind: scenario.owner, + }); + }); + + it('should list files', async () => { + const { create } = await createAndUploadFile({ + supertest: supertestWithoutAuth, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + auth: { user: scenario.user, space: null }, + }); + + const files = await listFiles({ + supertest: supertestWithoutAuth, + params: { kind: scenario.owner }, + auth: { user: scenario.user, space: null }, + }); + + expect(files.total).to.be(1); + expect(files.files[0].name).to.be(create.file.name); + }); + + it('should download a file', async () => { + const { create } = await createAndUploadFile({ + supertest: supertestWithoutAuth, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + auth: { user: scenario.user, space: null }, + }); + + const { body: buffer, header } = await downloadFile({ + supertest, + auth: { user: scenario.user, space: null }, + fileId: create.file.id, + kind: scenario.owner, + mimeType: 'image/png', + fileName: 'test.png', + }); + + expect(header['content-type']).to.eql('image/png'); + expect(header['content-disposition']).to.eql('attachment; filename="test.png"'); + expect(buffer.toString('utf8')).to.eql('abc'); + }); + + it('should upload a file', async () => { + const createResult = await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + }); + + const uploadResult = await uploadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + data: 'abc', + kind: scenario.owner, + mimeType: 'image/png', + fileId: createResult.file.id, + }); + + expect(uploadResult.ok).to.be(true); + expect(uploadResult.size).to.be(3); + }); + }); + }); + } + }); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/cases/index.ts b/x-pack/test/api_integration/apis/cases/index.ts index f12e43b34d784..5bce534873f10 100644 --- a/x-pack/test/api_integration/apis/cases/index.ts +++ b/x-pack/test/api_integration/apis/cases/index.ts @@ -34,5 +34,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./privileges')); loadTestFile(require.resolve('./suggest_user_profiles')); loadTestFile(require.resolve('./bulk_get_user_profiles')); + loadTestFile(require.resolve('./files')); }); } diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index d4d4c48c60336..1d63f8872776f 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { estypes } from '@elastic/elasticsearch'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -24,14 +25,26 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(200); + const geoPointFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( + (fieldStat: estypes.ClusterStatsFieldTypes) => { + return fieldStat.name === 'geo_point'; + } + ); + expect(geoPointFieldStats.count).to.be(7); + expect(geoPointFieldStats.index_count).to.be(6); + + const geoShapeFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( + (fieldStat: estypes.ClusterStatsFieldTypes) => { + return fieldStat.name === 'geo_shape'; + } + ); + expect(geoShapeFieldStats.count).to.be(3); + expect(geoShapeFieldStats.index_count).to.be(3); + const mapUsage = apiResponse.stack_stats.kibana.plugins.maps; delete mapUsage.timeCaptured; expect(mapUsage).eql({ - geoShapeAggLayersCount: 1, - indexPatternsWithGeoFieldCount: 6, - indexPatternsWithGeoPointFieldCount: 4, - indexPatternsWithGeoShapeFieldCount: 2, mapsTotalCount: 27, basemaps: {}, joins: { term: { min: 1, max: 1, total: 3, avg: 0.1111111111111111 } }, diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts index aba182274c9a3..2e328e91f8c25 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts @@ -54,7 +54,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['some string that does not exist'], @@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['message of document 0'], @@ -130,7 +130,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['generate_test_data/simple_logs'], @@ -166,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, query: JSON.stringify({ diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts index 4ac098f95764d..3ccfc4c267c7b 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesSummaryRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts index 9fa041108964e..8eef4dc32df1c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts @@ -248,7 +248,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'cpu', value: null, max: 0.47105555555555556, - avg: 0.0672936507936508, + avg: 0.47105555555555556, }; expect(snapshot).to.have.property('nodes'); diff --git a/x-pack/test/api_integration/apis/ml/config.ts b/x-pack/test/api_integration/apis/ml/config.ts index 925eda2a5bea8..0f467331cbf88 100644 --- a/x-pack/test/api_integration/apis/ml/config.ts +++ b/x-pack/test/api_integration/apis/ml/config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseIntegrationTestsConfig.getAll(), testFiles: [require.resolve('.')], junit: { - reportName: 'Chrome X-Pack UI Functional Tests - ml', + reportName: 'X-Pack API Integration Tests - ml', }, }; } diff --git a/x-pack/test/api_integration/apis/transform/config.ts b/x-pack/test/api_integration/apis/transform/config.ts index 7dac20656b8ea..3689c884d1000 100644 --- a/x-pack/test/api_integration/apis/transform/config.ts +++ b/x-pack/test/api_integration/apis/transform/config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseIntegrationTestsConfig.getAll(), testFiles: [require.resolve('.')], junit: { - reportName: 'Chrome X-Pack UI Functional Tests - transform', + reportName: 'X-Pack API Integration Tests - transform', }, }; } diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts index e2dcafc0af815..7a454ad5c9687 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts @@ -5,14 +5,13 @@ * 2.0. */ import expect from '@kbn/expect'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { timerange, serviceMap } from '@kbn/apm-synthtrace-client'; import { APIClientRequestParamsOf, APIReturnType, } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { generateTrace } from '../traces/generate_trace'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -44,30 +43,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Service map', { config: 'trial', archives: [] }, () => { describe('optional kuery param', () => { before(async () => { - const go = apm - .service({ name: 'synthbeans-go', environment: 'test', agentName: 'go' }) - .instance('synthbeans-go'); - const java = apm - .service({ name: 'synthbeans-java', environment: 'test', agentName: 'java' }) - .instance('synthbeans-java'); - const node = apm - .service({ name: 'synthbeans-node', environment: 'test', agentName: 'nodejs' }) - .instance('synthbeans-node'); - const events = timerange(start, end) .interval('15m') .rate(1) - .generator((timestamp) => { - return [ - generateTrace(timestamp, [go, java]), - generateTrace(timestamp, [java, go], 'redis'), - generateTrace(timestamp, [node], 'redis'), - generateTrace(timestamp, [node, java, go], 'elasticsearch').defaults({ - 'labels.name': 'node-java-go-es', - }), - generateTrace(timestamp, [go, node, java]), - ]; - }); + .generator( + serviceMap({ + services: [ + { 'synthbeans-go': 'go' }, + { 'synthbeans-java': 'java' }, + { 'synthbeans-node': 'nodejs' }, + ], + definePaths([go, java, node]) { + return [ + [go, java], + [java, go, 'redis'], + [node, 'redis'], + { + path: [node, java, go, 'elasticsearch'], + transaction: (t) => t.defaults({ 'labels.name': 'node-java-go-es' }), + }, + [go, node, java], + ]; + }, + }) + ); await synthtraceEsClient.index(events); }); diff --git a/x-pack/test/cases_api_integration/common/lib/api/files.ts b/x-pack/test/cases_api_integration/common/lib/api/files.ts new file mode 100644 index 0000000000000..05450e9da2cc9 --- /dev/null +++ b/x-pack/test/cases_api_integration/common/lib/api/files.ts @@ -0,0 +1,226 @@ +/* + * 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 SuperTest from 'supertest'; +import { apiRoutes as fileApiRoutes } from '@kbn/files-plugin/public/files_client/files_client'; +import { BaseFilesClient } from '@kbn/shared-ux-file-types'; +import { superUser } from '../authentication/users'; +import { User } from '../authentication/types'; +import { getSpaceUrlPrefix } from './helpers'; + +export const downloadFile = async ({ + supertest, + fileId, + kind, + mimeType, + fileName, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + fileId: string; + kind: string; + mimeType: string; + fileName: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const result = await supertest + .get( + `${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getDownloadRoute(kind, fileId, fileName)}` + ) + .set('accept', mimeType) + .buffer() + .expect(expectedHttpCode); + + return result; +}; + +export interface FileDescriptor { + kind: string; + id: string; +} + +export const deleteFiles = async ({ + supertest, + files, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + files: FileDescriptor[]; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + await Promise.all( + files.map(async (fileInfo) => { + return await supertest + .delete( + `${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getDeleteRoute( + fileInfo.kind, + fileInfo.id + )}` + ) + .set('kbn-xsrf', 'true') + .auth(auth.user.username, auth.user.password) + .send() + .expect(expectedHttpCode); + }) + ); +}; + +export const deleteAllFiles = async ({ + supertest, + kind, + auth = { user: superUser, space: null }, + expectedHttpCode = 200, +}: { + supertest: SuperTest.SuperTest; + kind: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + const files = await listFiles({ supertest, params: { kind }, auth, expectedHttpCode }); + + await deleteFiles({ + supertest, + files: files.files.map((fileInfo) => ({ kind, id: fileInfo.id })), + auth, + expectedHttpCode, + }); +}; + +export const getFileById = async ({ + supertest, + id, + kind, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + id: string; + kind: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { body } = await supertest + .get(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getByIdRoute(kind, id)}`) + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return body; +}; + +export const listFiles = async ({ + supertest, + params, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + params: Parameters[0]; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { page, perPage, kind, ...rest } = params; + + const { body } = await supertest + .post(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getListRoute(kind)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .query({ page, perPage }) + .send(rest) + .expect(expectedHttpCode); + + return body; +}; + +type CreateFileSchema = Omit[0], 'mimeType'> & { + mimeType: string; +}; + +export const createFile = async ({ + supertest, + params, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + params: CreateFileSchema; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { kind, ...rest } = params; + const { body } = await supertest + .post(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getCreateFileRoute(kind)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .send(rest) + .expect(expectedHttpCode); + + return body; +}; + +export const uploadFile = async ({ + supertest, + data, + kind, + fileId, + mimeType, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + data: string | object; + kind: string; + fileId: string; + mimeType: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { body } = await supertest + .put(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getUploadRoute(kind, fileId)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .set('Content-Type', mimeType) + .send(data) + .expect(expectedHttpCode); + + return body; +}; + +export const createAndUploadFile = async ({ + supertest, + data, + createFileParams, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + data: string | object; + createFileParams: CreateFileSchema; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + const createFileResult = await createFile({ + supertest, + params: createFileParams, + expectedHttpCode, + auth, + }); + + const uploadFileResult = await uploadFile({ + supertest, + data, + fileId: createFileResult.file.id, + mimeType: createFileParams.mimeType, + kind: createFileParams.kind, + auth, + }); + + return { create: createFileResult, upload: uploadFileResult }; +}; diff --git a/x-pack/test/cases_api_integration/common/lib/api/index.ts b/x-pack/test/cases_api_integration/common/lib/api/index.ts index c15c19232edf6..3bf0c470b9ba2 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/index.ts @@ -54,6 +54,7 @@ export * from './user_actions'; export * from './user_profiles'; export * from './omit'; export * from './configuration'; +export * from './files'; export { getSpaceUrlPrefix } from './helpers'; function toArray(input: T | T[]): T[] { diff --git a/x-pack/test/cases_api_integration/common/lib/authentication/index.ts b/x-pack/test/cases_api_integration/common/lib/authentication/index.ts index d425eae5a373c..195549561571b 100644 --- a/x-pack/test/cases_api_integration/common/lib/authentication/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/authentication/index.ts @@ -20,9 +20,8 @@ export const getUserInfo = (user: User): UserInfo => ({ export const createSpaces = async (getService: CommonFtrProviderContext['getService']) => { const spacesService = getService('spaces'); - for (const space of spaces) { - await spacesService.create(space); - } + + await Promise.all(spaces.map((space) => spacesService.create(space))); }; /** @@ -51,23 +50,16 @@ export const createUsersAndRoles = async ( }); }; - for (const role of rolesToCreate) { - await createRole(role); - } - - for (const user of usersToCreate) { - await createUser(user); - } + await Promise.all(rolesToCreate.map((role) => createRole(role))); + await Promise.all(usersToCreate.map((user) => createUser(user))); }; export const deleteSpaces = async (getService: CommonFtrProviderContext['getService']) => { const spacesService = getService('spaces'); - for (const space of spaces) { - try { - await spacesService.delete(space.id); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(spaces.map((space) => spacesService.delete(space.id))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } }; @@ -78,20 +70,16 @@ export const deleteUsersAndRoles = async ( ) => { const security = getService('security'); - for (const user of usersToDelete) { - try { - await security.user.delete(user.username); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(usersToDelete.map((user) => security.user.delete(user.username))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } - for (const role of rolesToDelete) { - try { - await security.role.delete(role.name); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(rolesToDelete.map((role) => security.role.delete(role.name))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts index 123e7bdbed2e5..a15b6d799adea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts @@ -866,6 +866,7 @@ export default ({ getService }: FtrProviderContext): void => { errors: [ { rule_id: 'rule-1', + id: '123', error: { status_code: 404, message: '1 connector is missing. Connector id missing is: 123', @@ -881,6 +882,7 @@ export default ({ getService }: FtrProviderContext): void => { action_connectors_errors: [ { rule_id: 'rule-1', + id: '123', error: { status_code: 404, message: '1 connector is missing. Connector id missing is: 123', @@ -1153,6 +1155,7 @@ export default ({ getService }: FtrProviderContext): void => { errors: [ { rule_id: 'rule-2', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aa22', error: { status_code: 404, message: @@ -1173,6 +1176,7 @@ export default ({ getService }: FtrProviderContext): void => { '1 connector is missing. Connector id missing is: cabc78e0-9031-11ed-b076-53cc4d57aa22', }, rule_id: 'rule-2', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aa22', }, ], action_connectors_warnings: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts index ded6e5487dbe5..88976b8ad3b18 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { v4 as uuidv4 } from 'uuid'; import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { orderBy } from 'lodash'; @@ -26,16 +27,54 @@ import { import { FtrProviderContext } from '../../common/ftr_provider_context'; import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries'; import { deleteAllExceptions } from '../../../lists_api_integration/utils'; +import { dataGeneratorFactory } from '../../utils/data_generator'; import { largeArraysBuckets } from './mocks/new_terms'; import { removeRandomValuedProperties } from './utils'; +const historicalWindowStart = '2022-10-13T05:00:04.000Z'; +const ruleExecutionStart = '2022-10-19T05:00:04.000Z'; + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); + const { indexEnhancedDocuments } = dataGeneratorFactory({ + es, + index: 'new_terms', + log, + }); + + /** + * indexes 2 sets of documents: + * - documents in historical window + * - documents in rule execution window + * @returns id of documents + */ + const newTermsTestExecutionSetup = async ({ + historicalDocuments, + ruleExecutionDocuments, + }: { + historicalDocuments: Array>; + ruleExecutionDocuments: Array>; + }) => { + const testId = uuidv4(); + + await indexEnhancedDocuments({ + interval: [historicalWindowStart, ruleExecutionStart], + id: testId, + documents: historicalDocuments, + }); + + await indexEnhancedDocuments({ + id: testId, + documents: ruleExecutionDocuments, + }); + + return testId; + }; describe('New terms type rules', () => { before(async () => { @@ -246,13 +285,36 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 1 alert for unique combination of existing terms', async () => { + // historical window documents + const historicalDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.1' }, + }, + { + host: { name: 'host-1', ip: '127.0.0.2' }, + }, + ]; + + // rule execution documents + const ruleExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.2' }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + // ensure there are no alerts for single new terms fields, it means values are not new const rule: NewTermsRuleCreateProps = { ...getCreateNewTermsRulesSchemaMock('rule-1', true), index: ['new_terms'], new_terms_fields: ['host.name', 'host.ip'], - from: '2020-10-19T05:00:04.000Z', - history_window_start: '2020-10-13T05:00:04.000Z', + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, }; // shouldn't be terms for 'host.ip' const hostIpPreview = await previewRule({ @@ -285,12 +347,36 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 5 alerts, 1 for each new unique combination in 2 fields', async () => { + const historicalDocuments = [ + { + 'source.ip': ['192.168.1.1'], + tags: ['tag-1', 'tag-2'], + }, + { + 'source.ip': ['192.168.1.1'], + tags: ['tag-1'], + }, + ]; + + const ruleExecutionDocuments = [ + { + 'source.ip': ['192.168.1.1', '192.168.1.2'], + tags: ['tag-new-1', 'tag-2', 'tag-new-3'], + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + const rule: NewTermsRuleCreateProps = { ...getCreateNewTermsRulesSchemaMock('rule-1', true), index: ['new_terms'], new_terms_fields: ['source.ip', 'tags'], - from: '2020-10-19T05:00:04.000Z', - history_window_start: '2020-10-13T05:00:04.000Z', + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, }; const { previewId } = await previewRule({ supertest, rule }); @@ -313,12 +399,24 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 1 alert for unique combination of terms, one of which is a number', async () => { + const historicalDocuments = [ + { user: { name: 'user-0', id: 0 } }, + { user: { name: 'user-1', id: 1 } }, + ]; + const ruleExecutionDocuments = [{ user: { name: 'user-0', id: 1 } }]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + const rule: NewTermsRuleCreateProps = { ...getCreateNewTermsRulesSchemaMock('rule-1', true), index: ['new_terms'], new_terms_fields: ['user.name', 'user.id'], - from: '2020-10-19T05:00:04.000Z', - history_window_start: '2020-10-13T05:00:04.000Z', + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, }; const { previewId } = await previewRule({ supertest, rule }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts index 3c5368b7a23ad..a4b91a7002183 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts @@ -6,8 +6,6 @@ */ import expect from 'expect'; -import { v4 as uuidv4 } from 'uuid'; - import { deleteAllRules, deleteSignalsIndex, @@ -15,7 +13,7 @@ import { getRuleForSignalTesting, previewRule, } from '../../utils'; -import { indexDocumentsFactory } from '../../utils/data_generator'; +import { dataGeneratorFactory, enhanceDocument } from '../../utils/data_generator'; import { FtrProviderContext } from '../../common/ftr_provider_context'; const getQueryRule = (docIdToQuery: string) => ({ @@ -23,12 +21,6 @@ const getQueryRule = (docIdToQuery: string) => ({ query: `id: "${docIdToQuery}"`, }); -const getDocument = (id: string, doc: Record) => ({ - id, - '@timestamp': new Date().toISOString(), - ...doc, -}); - // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); @@ -36,7 +28,7 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const log = getService('log'); - const indexDocuments = indexDocumentsFactory({ + const { indexListOfDocuments } = dataGeneratorFactory({ es, index: 'ecs_non_compliant', log, @@ -49,13 +41,12 @@ export default ({ getService }: FtrProviderContext) => { * 3. return created preview alert and errors logs */ const indexAndCreatePreviewAlert = async (document: Record) => { - const documentId = uuidv4(); - - await indexDocuments([getDocument(documentId, document)]); + const enhancedDocument = enhanceDocument({ document }); + await indexListOfDocuments([enhancedDocument]); const { previewId, logs } = await previewRule({ supertest, - rule: getQueryRule(documentId), + rule: getQueryRule(enhancedDocument.id), }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -323,5 +314,63 @@ export default ({ getService }: FtrProviderContext) => { // invalid ECS field is getting removed expect(alertSource).not.toHaveProperty('dll.code_signature.valid'); }); + + describe('multi-fields', () => { + it('should not add multi field .text to ecs compliant nested source', async () => { + const document = { + process: { + command_line: 'string longer than 10 characters', + }, + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource).toHaveProperty('process', document.process); + expect(alertSource).not.toHaveProperty('process.command_line.text'); + }); + + it('should not add multi field .text to ecs compliant flattened source', async () => { + const document = { + 'process.command_line': 'string longer than 10 characters', + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource?.['process.command_line']).toEqual(document['process.command_line']); + expect(alertSource).not.toHaveProperty('process.command_line.text'); + }); + + it('should not add multi field .text to ecs non compliant nested source', async () => { + const document = { + nonEcs: { + command_line: 'string longer than 10 characters', + }, + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource).toHaveProperty('nonEcs', document.nonEcs); + expect(alertSource).not.toHaveProperty('nonEcs.command_line.text'); + }); + + it('should not add multi field .text to ecs non compliant flattened source', async () => { + const document = { + 'nonEcs.command_line': 'string longer than 10 characters', + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource?.['nonEcs.command_line']).toEqual(document['nonEcs.command_line']); + expect(alertSource).not.toHaveProperty('nonEcs.command_line.text'); + }); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts index c77ac2049fde5..2f769a1a1168f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts @@ -49,7 +49,7 @@ import { setSignalStatus, } from '../../utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { indexDocumentsFactory } from '../../utils/data_generator'; +import { dataGeneratorFactory } from '../../utils/data_generator'; import { patchRule } from '../../utils/patch_rule'; /** @@ -730,7 +730,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('with a suppression time window', async () => { - const indexDocuments = indexDocumentsFactory({ + const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ es, index: 'ecs_compliant', log, @@ -758,7 +758,7 @@ export default ({ getService }: FtrProviderContext) => { name: 'agent-1', }, }; - await indexDocuments([firstDocument, firstDocument]); + await indexListOfDocuments([firstDocument, firstDocument]); const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['ecs_compliant']), @@ -799,7 +799,7 @@ export default ({ getService }: FtrProviderContext) => { }; // Add a new document, then disable and re-enable to trigger another rule run. The second doc should // trigger an update to the existing alert without changing the timestamp - await indexDocuments([secondDocument, secondDocument]); + await indexListOfDocuments([secondDocument, secondDocument]); await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); @@ -839,7 +839,7 @@ export default ({ getService }: FtrProviderContext) => { name: 'agent-1', }, }; - await indexDocuments([firstDocument, firstDocument]); + await indexListOfDocuments([firstDocument, firstDocument]); const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['ecs_compliant']), @@ -875,7 +875,7 @@ export default ({ getService }: FtrProviderContext) => { }; // Add new documents, then disable and re-enable to trigger another rule run. The second doc should // trigger a new alert since the first one is now closed. - await indexDocuments([secondDocument, secondDocument]); + await indexListOfDocuments([secondDocument, secondDocument]); await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); @@ -1160,7 +1160,7 @@ export default ({ getService }: FtrProviderContext) => { ingested: '2020-10-28T06:10:00.000Z', }, }; - await indexDocuments([docWithoutOverride, docWithOverride]); + await indexListOfDocuments([docWithoutOverride, docWithOverride]); const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['ecs_compliant']), @@ -1209,26 +1209,22 @@ export default ({ getService }: FtrProviderContext) => { it('should generate and update up to max_signals alerts', async () => { const id = uuidv4(); const timestamp = '2020-10-28T06:00:00.000Z'; - const docs = Array(150) - .fill({}) - .map((_, i) => ({ - id, - '@timestamp': timestamp, - agent: { - name: `agent-${i}`, - }, - })); const laterTimestamp = '2020-10-28T07:00:00.000Z'; - const laterDocs = Array(150) - .fill({}) - .map((_, i) => ({ - id, - '@timestamp': laterTimestamp, - agent: { - name: `agent-${i}`, - }, - })); - await indexDocuments([...docs, ...laterDocs]); + + await Promise.all( + [timestamp, laterTimestamp].map((t) => + indexGeneratedDocuments({ + docsCount: 150, + seed: (index) => ({ + id, + '@timestamp': t, + agent: { + name: `agent-${index}`, + }, + }), + }) + ) + ); const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['ecs_compliant']), @@ -1315,7 +1311,7 @@ export default ({ getService }: FtrProviderContext) => { name: 'agent-2', }, }; - await indexDocuments([firstDoc, secondDoc, thirdDoc]); + await indexListOfDocuments([firstDoc, secondDoc, thirdDoc]); const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['ecs_compliant']), diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts index 0f92db72d3c2a..8b3e4db6fa542 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { get, isEqual } from 'lodash'; +import { get, isEqual, omit } from 'lodash'; import expect from '@kbn/expect'; import { ALERT_REASON, @@ -19,6 +19,7 @@ import { VERSION, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; +import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; @@ -32,6 +33,7 @@ import { ALERT_ORIGINAL_EVENT_MODULE, ALERT_ORIGINAL_TIME, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; import { previewRule, getOpenSignals, @@ -41,7 +43,6 @@ import { createRule, } from '../../utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; - const format = (value: unknown): string => JSON.stringify(value, null, 2); // Asserts that each expected value is included in the subject, independent of @@ -54,6 +55,85 @@ const assertContains = (subject: unknown[], expected: unknown[]) => ) ); +const createThreatMatchRule = ({ + name = 'Query with a rule id', + index = ['auditbeat-*'], + query = '*:*', + // eslint-disable-next-line @typescript-eslint/naming-convention + rule_id = 'rule-1', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_indicator_path = 'threat.indicator', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_query = 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_index = ['auditbeat-*'], + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_mapping = [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + override = {}, +}: { + threat_mapping?: ThreatMapping; + name?: string; + query?: string; + rule_id?: string; + index?: string[]; + threat_index?: string[]; + threat_query?: string; + threat_indicator_path?: string; + override?: any; +} = {}): ThreatMatchRuleCreateProps => ({ + description: 'Detecting root and admin users', + name, + severity: 'high', + index, + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id, + from: '1900-01-01T00:00:00.000Z', + query, + threat_query, + threat_index, + threat_mapping, + threat_filters: [], + threat_indicator_path, + ...override, +}); + +function alertsAreTheSame(alertsA: any[], alertsB: any[]): void { + const mapAlert = (alert: any) => { + return omit(alert._source, [ + '@timestamp', + 'kibana.alert.last_detected', + 'kibana.rule.created_at', + 'kibana.rule.execution.uuid', + 'kibana.name.execution.uuid', + 'kibana.alert.rule.parameters', + 'kibana.alert.rule.rule_id', + 'kibana.alert.rule.name', + 'kibana.alert.rule.created_at', + 'kibana.alert.rule.updated_at', + 'kibana.alert.rule.uuid', + 'kibana.alert.rule.execution.uuid', + 'kibana.alert.start', + 'kibana.alert.reason', + 'kibana.alert.uuid', + ]); + }; + + expect(alertsA.map(mapAlert)).to.eql(alertsB.map(mapAlert)); +} + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -66,6 +146,8 @@ export default ({ getService }: FtrProviderContext) => { */ describe('Threat match type rules', () => { before(async () => { + // await deleteSignalsIndex(supertest, log); + // await deleteAllAlerts(supertest, log); await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -76,38 +158,19 @@ export default ({ getService }: FtrProviderContext) => { }); // First 2 test creates a real rule - remaining tests use preview API - it('should be able to execute and get 10 signals when doing a specific query (terms query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; + it('should be able to execute and get all signals when doing a specific query (terms query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule(); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -257,20 +320,8 @@ export default ({ getService }: FtrProviderContext) => { }), }); }); - it('should be able to execute and get 10 signals when doing a specific query (match query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + it('should be able to execute and get all signals when doing a specific query (match query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -288,12 +339,18 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -444,20 +501,64 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('terms and match should have the same alerts with pagination', async () => { + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + }); + + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + name: 'Math rule', + rule_id: 'rule-2', + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + }); + + const createdRuleTerm = await createRule(supertest, log, termRule); + const createdRuleMatch = await createRule(supertest, log, matchRule); + const alertsTerm = await getOpenSignals( + supertest, + log, + es, + createdRuleTerm, + RuleExecutionStatus.succeeded, + 100 + ); + const alertsMatch = await getOpenSignals( + supertest, + log, + es, + createdRuleMatch, + RuleExecutionStatus.succeeded, + 100 + ); + + alertsAreTheSame(alertsTerm.hits.hits, alertsMatch.hits.hits); + }); + it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -470,8 +571,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -479,19 +579,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -508,8 +596,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -517,19 +604,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - type: 'threat_match', - index: ['auditbeat-*'], - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -546,8 +621,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -557,35 +631,13 @@ export default ({ getService }: FtrProviderContext) => { describe('timeout behavior', () => { // TODO: unskip this and see if we can make it not flaky it.skip('will return an error if a rule execution exceeds the rule interval', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a short interval', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: '*:*', // broad query to take more time - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - concurrent_searches: 1, - interval: '1s', // short interval - items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow - }; + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + concurrent_searches: 1, + interval: '1s', // short interval + items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow + }, + }); const { logs } = await previewRule({ supertest, rule }); expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval'); @@ -602,20 +654,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow events down to 2 with a destination.ip - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -627,8 +666,9 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -690,18 +730,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -715,9 +745,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; - + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); expect(previewAlerts.length).equal(1); @@ -767,18 +795,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -801,8 +819,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -876,19 +893,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -916,8 +921,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1023,18 +1027,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'destination.ip:159.89.119.67', - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1048,14 +1042,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'destination.ip:159.89.119.67', + threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(2); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(2); - const threats = previewAlerts.map((hit) => hit._source?.threat); + const threats = termPreviewAlerts.map((hit) => hit._source?.threat); expect(threats).to.eql([ { enrichments: [ @@ -1108,21 +1124,12 @@ export default ({ getService }: FtrProviderContext) => { ], }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1136,14 +1143,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threat] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; @@ -1185,21 +1214,12 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1222,18 +1242,57 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threatTerm] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; - assertContains(threat.enrichments, [ + const [threatMatch] = matchPrevieAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + assertContains(threatTerm.enrichments, [ { feed: {}, indicator: { @@ -1294,22 +1353,23 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + const sortEnrichments = (a: any, b: any) => { + const atomicA = a.matched.atomic.toString(); + const atomicB = b.matched.atomic.toString(); + if (atomicA === atomicB) { + return a.indicator.description > b.indicator.description ? 1 : -1; + } + return atomicA > atomicB ? 1 : -1; + }; + + expect(threatTerm.enrichments.sort(sortEnrichments)).to.be.eql( + threatMatch.enrichments.sort(sortEnrichments) + ); }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1337,8 +1397,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1444,19 +1503,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be enriched with host risk score', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '*:*', threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity threat_mapping: [ // We match host.name against host.name { @@ -1469,8 +1518,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md b/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md new file mode 100644 index 0000000000000..e737e7b133929 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md @@ -0,0 +1,606 @@ +# Data Generator for functional tests + +Helper to generate and index documents for using in Kibana functional tests + +- [Data Generator for functional tests](#data-generator-for-functional-tests) + - [DataGenerator](#datagenerator) + - [Initialization](#initialization) + - [Prerequisites](#prerequisites) + - [dataGeneratorFactory](#datageneratorfactory) + - [methods](#methods) + - [**indexListOfDocuments**](#indexlistofdocuments) + - [**indexGeneratedDocuments**](#indexgenerateddocuments) + - [**indexEnhancedDocuments**](#indexenhanceddocuments) + - [Utils](#utils) + - [**generateDocuments**](#generatedocuments) + - [**enhanceDocument**](#enhancedocument) + - [**enhanceDocuments**](#enhancedocuments) + - [Usage](#usage) + - [create test query rule that queries indexed documents within a test](#create-test-query-rule-that-queries-indexed-documents-within-a-test) + +## DataGenerator + +### Initialization + + +#### Prerequisites +1. Create index mappings in `x-pack/test/functional/es_archives/security_solution` + - create folder for index `foo_bar` + - add mappings file `mappings.json` in it + +
    + x-pack/test/functional/es_archives/security_solution/foo_bar/mappings.json + + ```JSON + { + "type": "index", + "value": { + "index": "foo_bar", + "mappings": { + "properties": { + "id": { + "type": "keyword" + }, + "@timestamp": { + "type": "date" + }, + "foo": { + "type": "keyword" + }, + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } + } + ``` +
    +2. Add in `before` of the test file index initialization + + ```ts + const esArchiver = getService('esArchiver'); + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/foo_bar' + ); + }); + + ``` + +3. Add in `after` of the test file index removal + + ```ts + const esArchiver = getService('esArchiver'); + + before(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/foo_bar' + ); + }); + + ``` + +#### dataGeneratorFactory + +`DataGeneratorParams` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| es | ES client | `ESClient` | +| index | index where document will be added | `string` | +| log | log client | `LogClient`| + +1. import and initialize factory + + ```ts + import { dataGeneratorFactory } from '../../utils/data_generator'; + + const es = getService('es'); + const log = getService('log'); + + const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ + es, + index: 'foo_bar', + log, + }); + + ``` +2. Factory will return 2 methods which can be used to index documents into `foo_bar` + +where `getService` is method from `FtrProviderContext` + +### methods + +#### **indexListOfDocuments** + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| documents | list of documents to index | `Record` | + +Will index list of documents to `foo_bar` index as defined in `dataGeneratorFactory` params + +```ts + await indexListOfDocuments([{ foo: "bar" }, { id: "test-1" }]) + +``` + +#### **indexGeneratedDocuments** + +Will generate 10 documents in defined interval and index them in `foo_bar` index as defined in `dataGeneratorFactory` params +Method receives same parameters as [generateDocuments](#generateDocuments) util. + +```ts + await indexGeneratedDocuments({ + docsCount: 10, + interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], + seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) + }) + +``` + +#### **indexEnhancedDocuments** + +Will index list of enhanced documents to `foo_bar` index as defined in `dataGeneratorFactory` params +Method receives same parameters as [enhanceDocuments](#enhanceDocuments) util. + +```ts + await indexEnhancedDocuments({ + interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }) + +``` + +## Utils + +### **generateDocuments** + +Util `generateDocuments` can generate list of documents based on basic seed function + + Seed callback will receive sequential number of document of document, generated id, timestamp. + Can be used to generate custom document with large set of options depends on needs. See examples below. + + | Property | Description | Type | + | --------------- | ------------------------------------------------------ | ------ | + | docsCount | number of documents to generate | `number` | + | seed | function that receives sequential number of document, generated id, timestamp as arguments and can used it create a document | `(index: number, id: string, timestamp: string) => Document` | + | interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | + +Examples: + + 1. Generate 10 documents with random id, timestamp in interval between '2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z', and field `seq` that represents sequential number of document + + ```ts + + const documents = generateDocuments({ + docsCount: 10, + interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], + seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "id": "87d3d231-13c8-4d03-9ae4-d40781b3b2d1", + "@timestamp": "2020-10-30T04:00:55.790Z", + "seq": 0 + }, + { + "id": "90b99797-d0da-460d-86fd-eca40bedff39", + "@timestamp": "2020-10-28T08:43:01.117Z", + "seq": 1 + }, + { + "id": "809c05be-f401-4e31-86e1-55be8af4fac4", + "@timestamp": "2020-10-29T15:06:23.054Z", + "seq": 2 + }, + { + "id": "a2720f82-5401-4eab-b2eb-444a8425c937", + "@timestamp": "2020-10-29T23:19:47.790Z", + "seq": 3 + }, + { + "id": "e36e4418-4e89-4388-97df-97085b3fca92", + "@timestamp": "2020-10-29T09:14:00.966Z", + "seq": 4 + }, + { + "id": "4747adb3-0603-4651-8c0f-0c7df037f779", + "@timestamp": "2020-10-28T14:23:50.500Z", + "seq": 5 + }, + { + "id": "1fbfd873-b0ca-4cda-9c96-9a044622e712", + "@timestamp": "2020-10-28T10:00:20.995Z", + "seq": 6 + }, + { + "id": "9173cf93-1f9f-4f91-be5e-1e6888cb3aae", + "@timestamp": "2020-10-28T08:52:27.830Z", + "seq": 7 + }, + { + "id": "53245337-e383-4b28-9975-acbd79901b7c", + "@timestamp": "2020-10-29T08:58:02.385Z", + "seq": 8 + }, + { + "id": "0c700d33-df10-426e-8f71-677f437923ec", + "@timestamp": "2020-10-29T16:33:10.240Z", + "seq": 9 + } + ] + ``` + +
    + + 2. Generate 3 identical documents `{foo: bar}` + + ```ts + + const documents = generateDocuments({ + docsCount: 3, + seed: () => ({ foo: 'bar' }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar" + }, + { + "foo": "bar" + }, + { + "foo": "bar" + } + ] + ``` + +
    + + 3. Generate 5 documents with custom ingested timestamp, with no interval. If interval not defined, timestamp will be current time + + ```ts + + const documents = generateDocuments({ + docsCount: 5, + seed: (i, id, timestamp) => ({ foo: 'bar', event: { ingested: timestamp } }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + } + ] + ``` + +
    + + 4. Generate 4 documents with custom if based on sequential number id + + ```ts + + const documents = generateDocuments({ + docsCount: 4, + seed: (i) => ({ foo: 'bar', id: `id-${i}`}) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar", + "id": "id-0" + }, + { + "foo": "bar", + "id": "id-1" + }, + { + "foo": "bar", + "id": "id-2" + }, + { + "foo": "bar", + "id": "id-3" + } + ] + ``` + +
    + + +### **enhanceDocument** + +Adds generated `uuidv4` id and current time as `@timestamp` to document if `id`, `timestamp` params are not specified + + +`EnhanceDocumentOptions` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| id | id for document | `string` _(optional)_ | +| timestamp | timestamp for document | `string` _(optional)_ | +| document | document to enhance | `Record` | + +Examples: + +1. Enhance document with generated `uuidv4` id and current time as `@timestamp` + + ```ts + const document = enhanceDocument({ + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "b501a64f-0dd4-4275-a38c-889be6a15a4d", + "@timestamp": "2023-02-15T17:21:21.429Z" + } + ``` + +
    + +2. Enhance document with generated `uuidv4` id and predefined timestamp + + + ```ts + const document = enhanceDocument({ + timestamp: '1996-02-15T13:02:37.531Z', + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "7b7460bf-e173-4744-af15-2c01ac52963b", + "@timestamp": "1996-02-15T13:02:37.531Z" + } + ``` + +
    + +3. Enhance document with predefined id and and current time as `@timestamp` + + + ```ts + const document = enhanceDocument({ + id: 'test-id', + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "test-id", + "@timestamp": "2023-02-15T17:21:21.429Z" + } + ``` +
    + +### **enhanceDocuments** + + + +Adds generated `uuidv4` `id` property to list of documents if `id` parameter is not specified. +Adds `@timestamp` in defined interval to list of documents. If it's not specified, `@timestamp` will be added as current time + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| documents | documents to enhance | `Record[]` | +| id | id for documents | `string` _(optional)_ | +| interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | + +Examples: + +1. Enhance documents with generated `uuidv4` id and current time as `@timestamp` + + ```ts + const documents = enhanceDocuments({ + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "c55ddd6b-3cf2-4ebf-94d6-4eeeb4e5b655", + "@timestamp": "2023-02-16T16:43:13.573Z" + }, + { + "foo": "bar-1", + "id": "61b157b9-5f1f-4d99-a5bf-072069f5139d", + "@timestamp": "2023-02-16T16:43:13.573Z" + }, + { + "foo": "bar-2", + "id": "04929927-6d9e-4ccc-b083-250e3fe2d7a7", + "@timestamp": "2023-02-16T16:43:13.573Z" + } + ] + ``` + +
    + +2. Enhance document with generated `uuidv4` id and timestamp in predefined interval + + ```ts + const documents = enhanceDocuments({ + interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "883a67cb-0a57-4711-bdf9-e8a394a52460", + "@timestamp": "1998-07-04T15:16:46.587Z" + }, + { + "foo": "bar-1", + "id": "70691d9e-1030-412f-8ae1-c6db50e90e91", + "@timestamp": "1998-05-15T07:00:52.339Z" + }, + { + "foo": "bar-2", + "id": "b2140328-5cc4-4532-947e-30b8fd830ed7", + "@timestamp": "1999-09-01T21:50:38.957Z" + } + ] + ``` + +
    + +3. Enhance documents with predefined id and and current time as `@timestamp` + + ```ts + const documents = enhanceDocuments({ + id: 'test-id', + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + }, + { + "foo": "bar-1", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + }, + { + "foo": "bar-2", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + } + ] + + ``` +
    + +## Usage + +### create test query rule that queries indexed documents within a test + +When documents generated and indexed, there might be a need to create a test rule that targets only these documents. So, documents generated in the test, will be used only in context of this test. + +There are few possible ways to do this + +1. Create new index every time for a new test. Thus, newly indexed documents, will be the only documents present in test index. It might be costly operation, as it will require to create new index for each test, that re-initialize dataGeneratorFactory, or delete index after rule's run + +2. Use the same id or specific field in documents. + For example: + + ```ts + + const id = uuidv4(); + const firstTimestamp = new Date().toISOString(); + const firstDocument = { + id, + '@timestamp': firstTimestamp, + agent: { + name: 'agent-1', + }, + }; + await indexListOfDocuments([firstDocument, firstDocument]); + + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['ecs_compliant']), + query: `id:${id}`, + }; + + + ``` + + All documents will have the same `id` and can be queried by following `id:${id}` + +3. Use utility method `getKQLQueryFromDocumentList` that will create query from all ids in generated documents + + ```ts + const { documents } = await indexGeneratedDocuments({ + docsCount: 4, + document: { foo: 'bar' }, + enhance: true, + }); + + const query = getKQLQueryFromDocumentList(documents); + const rule = { + ...getRuleForSignalTesting(['ecs_non_compliant']), + query, + }; + ``` + + util will generate the following query: `(id: "f6ca3ee1-407c-4685-a94b-11ef4ed5136b" or id: "2a7358b2-8cad-47ce-83b7-e4418c266f3e" or id: "9daec569-0ba1-4c46-a0c6-e340cee1c5fb" or id: "b03c2fdf-0ca1-447c-b8c6-2cc5a663ffe2")`, that will include all generated documents \ No newline at end of file diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts new file mode 100644 index 0000000000000..7842d105e40b0 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts @@ -0,0 +1,75 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { ToolingLog } from '@kbn/tooling-log'; +import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { indexDocuments } from './index_documents'; +import { generateDocuments } from './generate_documents'; +import { enhanceDocuments, EnhanceDocumentsOptions } from './enhance_documents'; +import type { GenerateDocumentsParams } from './generate_documents'; +import type { Document } from './types'; + +interface DataGeneratorParams { + es: Client; + documents: Array>; + index: string; + log: ToolingLog; +} + +interface DataGeneratorResponse { + response: BulkResponse; + documents: Document[]; +} + +interface DataGenerator { + indexListOfDocuments: (docs: Document[]) => Promise; + indexGeneratedDocuments: (params: GenerateDocumentsParams) => Promise; + indexEnhancedDocuments: (params: EnhanceDocumentsOptions) => Promise; +} + +/** + * initialize {@link DataGenerator} + * @param param.es - ES client + * @param params.index - index where document will be added + * @param params.log - logClient + * @returns methods of {@link DataGenerator} + */ +export const dataGeneratorFactory = ({ + es, + index, + log, +}: Omit): DataGenerator => { + return { + indexListOfDocuments: async (documents: DataGeneratorParams['documents']) => { + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + indexGeneratedDocuments: async (params: GenerateDocumentsParams) => { + const documents = generateDocuments(params); + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + indexEnhancedDocuments: async (params: EnhanceDocumentsOptions) => { + const documents = enhanceDocuments(params); + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + }; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts new file mode 100644 index 0000000000000..f2e244edb90b3 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { v4 as uuidv4 } from 'uuid'; + +interface EnhanceDocumentOptions { + id?: string; + timestamp?: string; + document: Record; +} + +/** + * enhances document with generated id and timestamp + * @param {string} options.id - optional id, if not provided randomly generated + * @param {string} options.timestamp - optional timestamp of document, if not provided current time + * @param {Record} options.document - document that will be enhanced + */ +export const enhanceDocument = (options: EnhanceDocumentOptions) => { + const id = options?.id ?? uuidv4(); + const timestamp = options?.timestamp ?? new Date().toISOString(); + return { + ...options.document, + id, + '@timestamp': timestamp, + }; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.ts new file mode 100644 index 0000000000000..6d5bce70b5432 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.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 type { IndexingInterval, Document } from './types'; +import { getTimestamp } from './utils'; +import { enhanceDocument } from './enhance_document'; + +export interface EnhanceDocumentsOptions { + interval?: IndexingInterval; + documents: Document[]; + id?: string; +} + +/** + * enhances documents with generated id and timestamp within interval + * @param {string} options.id - optional id, if not provided randomly generated + * @param {string} options.interval - optional interval of document, if not provided set as a current time + * @param {Record[]} options.documents - documents that will be enhanced + */ +export const enhanceDocuments = ({ documents, interval, id }: EnhanceDocumentsOptions) => { + return documents.map((document) => + enhanceDocument({ + document, + id, + timestamp: getTimestamp(interval), + }) + ); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts new file mode 100644 index 0000000000000..8bb05d50bea89 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { getTimestamp } from './utils'; + +import type { Document, IndexingInterval } from './types'; + +type DocumentSeedFunc = (index: number, id: string, timestamp: string) => Document; + +export interface GenerateDocumentsParams { + interval?: IndexingInterval; + docsCount: number; + seed: DocumentSeedFunc; +} + +/** + * + * @param param.interval - interval in which generate documents, defined by '@timestamp' field + * @param param.docsCount - number of document to generate + * @param param.seed - seed function. Function that receives index of document, generated id, timestamp as arguments and can used it create a document + * @returns generated Documents + */ +export const generateDocuments = ({ docsCount, interval, seed }: GenerateDocumentsParams) => { + const documents = []; + + for (let i = 0; i < docsCount; i++) { + const id = uuidv4(); + const timestamp = getTimestamp(interval); + + documents.push(seed(i, id, timestamp)); + } + + return documents; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.ts new file mode 100644 index 0000000000000..3347c2120d4a4 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.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 { Document } from './types'; + +/** + * returns KQL query from a list documents that includes all documents by their ids. + * it can be used later to create test rules that will query only these documents + * ```ts + * const documents = [ + { + foo: 'bar', + id: 'f07df596-65ec-4ab1-b0b2-f3b69558ed26', + '@timestamp': '2020-10-29T07:10:51.989Z', + }, + { + foo: 'bar', + id: 'e07614f9-1dc5-4849-90c4-31362bbdf8d0', + '@timestamp': '2020-10-30T00:32:48.987Z', + }, + { + foo: 'test', + id: 'e03a5b12-77e6-4aa3-b0be-fbe5b0843f07', + '@timestamp': '2020-10-29T03:40:35.318Z', + }, + ]; + + const query = getKQLQueryFromDocumentList(documents); + + // query equals to + // (id: "f07df596-65ec-4ab1-b0b2-f3b69558ed26" or id: "e07614f9-1dc5-4849-90c4-31362bbdf8d0" or id: "e03a5b12-77e6-4aa3-b0be-fbe5b0843f07") + * ``` + */ +export const getKQLQueryFromDocumentList = (documents: Document[]) => { + const orClauses = documents.map(({ id }) => `id: "${id}"`).join(' or '); + + return `(${orClauses})`; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts index 95c65cf5171eb..acc6307e62dcd 100644 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts @@ -5,4 +5,8 @@ * 2.0. */ -export * from './index_documents'; +export * from './data_generator_factory'; +export * from './enhance_document'; +export * from './enhance_documents'; +export * from './generate_documents'; +export * from './get_kql_query_from_documents_list'; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts index 6e850ee54aefb..5408e11b25015 100644 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts @@ -8,10 +8,12 @@ import type { Client } from '@elastic/elasticsearch'; import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ToolingLog } from '@kbn/tooling-log'; + interface IndexDocumentsParams { es: Client; documents: Array>; index: string; + log: ToolingLog; } type IndexDocuments = (params: IndexDocumentsParams) => Promise; @@ -19,28 +21,19 @@ type IndexDocuments = (params: IndexDocumentsParams) => Promise; /** * Indexes documents into provided index */ -export const indexDocuments: IndexDocuments = async ({ es, documents, index }) => { +export const indexDocuments: IndexDocuments = async ({ es, documents, index, log }) => { const operations = documents.flatMap((doc: object) => [{ index: { _index: index } }, doc]); - return es.bulk({ refresh: true, operations }); -}; + const response = await es.bulk({ refresh: true, operations }); -export const indexDocumentsFactory = ({ - es, - index, - log, -}: Omit & { log: ToolingLog }) => { - return async (documents: Array>) => { - const response = await indexDocuments({ es, index, documents }); - // throw error if document wasn't indexed, so test will be terminated earlier and no false positives can happen - response.items.some(({ index: responseIndex } = {}) => { - if (responseIndex?.error) { - log.error( - `Failed to index document in non_ecs_fields test suits: "${responseIndex.error?.reason}"` - ); - throw Error(responseIndex.error.message); - } - }); - return response; - }; + // throw error if document wasn't indexed, so test will be terminated earlier and no false positives can happen + response.items.some(({ index: responseIndex } = {}) => { + if (responseIndex?.error) { + log.error( + `Failed to index document in non_ecs_fields test suits: "${responseIndex.error?.reason}"` + ); + throw Error(responseIndex.error.message); + } + }); + return response; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/types.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/types.ts new file mode 100644 index 0000000000000..bbf54be68cfee --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/types.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 type IndexingInterval = [string | Date, string | Date]; + +export type Document = Record; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts new file mode 100644 index 0000000000000..e828767a39650 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts @@ -0,0 +1,16 @@ +/* + * 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 faker from 'faker'; +import type { IndexingInterval } from './types'; + +export const getTimestamp = (interval?: IndexingInterval) => { + if (interval) { + return faker.date.between(...interval).toISOString(); + } + + return new Date().toISOString(); +}; diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts index 949aaa65008c5..9a65bd0f2f95f 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts @@ -76,7 +76,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.dashboard.clickNewDashboard(); } - describe('dashboard maps by value', function () { + // Failing: See https://github.com/elastic/kibana/issues/152476 + describe.skip('dashboard maps by value', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( diff --git a/x-pack/test/functional/apps/infra/constants.ts b/x-pack/test/functional/apps/infra/constants.ts index 248c4f49a16c7..b1739f9a489d7 100644 --- a/x-pack/test/functional/apps/infra/constants.ts +++ b/x-pack/test/functional/apps/infra/constants.ts @@ -42,3 +42,5 @@ export const ML_JOB_IDS = [ ]; export const HOSTS_LINK_LOCAL_STORAGE_KEY = 'inventoryUI:hostsLinkClicked'; + +export const HOSTS_VIEW_PATH = 'metrics/hosts'; diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 4f3166c80afb9..1f1773505ffef 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -6,9 +6,11 @@ */ import expect from '@kbn/expect'; +import { enableInfrastructureHostsView } from '@kbn/observability-plugin/common'; +import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import moment from 'moment'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { DATES, HOSTS_LINK_LOCAL_STORAGE_KEY } from './constants'; +import { DATES, HOSTS_LINK_LOCAL_STORAGE_KEY, HOSTS_VIEW_PATH } from './constants'; const START_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); @@ -18,6 +20,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const browser = getService('browser'); + const find = getService('find'); const security = getService('security'); const pageObjects = getPageObjects([ 'common', @@ -29,9 +32,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); // Helpers + const setHostViewEnabled = (value: boolean = true) => + kibanaServer.uiSettings.update({ [enableInfrastructureHostsView]: value }); - const loginWithReadOnlyUserAndNavigateToInfra = async () => { - await security.role.create('global_hosts_read_privileges_role', { + const loginWithReadOnlyUser = async () => { + const roleCreation = security.role.create('global_hosts_read_privileges_role', { elasticsearch: { indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], }, @@ -46,13 +51,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ], }); - await security.user.create('global_hosts_read_privileges_user', { + const userCreation = security.user.create('global_hosts_read_privileges_user', { password: 'global_hosts_read_privileges_user-password', roles: ['global_hosts_read_privileges_role'], full_name: 'test user', }); - await pageObjects.security.forceLogout(); + const logout = pageObjects.security.forceLogout(); + + await Promise.all([roleCreation, userCreation, logout]); await pageObjects.security.login( 'global_hosts_read_privileges_user', @@ -61,87 +68,55 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expectSpaceSelector: false, } ); - - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); }; - const logoutAndDeleteReadOnlyUser = async () => { - await pageObjects.security.forceLogout(); - await Promise.all([ + const logoutAndDeleteReadOnlyUser = () => + Promise.all([ + pageObjects.security.forceLogout(), security.role.delete('global_hosts_read_privileges_role'), security.user.delete('global_hosts_read_privileges_user'), ]); - }; - const navigateAndDisableHostView = async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.common.navigateToUrl('management', 'kibana/settings', { - basePath: `/s/default`, - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - shouldUseHashForSubUrl: false, - }); - await pageObjects.settings.toggleAdvancedSettingCheckbox( - 'observability:enableInfrastructureHostsView', - false - ); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - }; - - const navigateAndEnableHostView = async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewLink(); - await pageObjects.infraHostsView.clickEnableHostViewButton(); - }; + const enableHostView = () => pageObjects.infraHostsView.clickEnableHostViewButton(); // Tests - describe('Hosts view', function () { + describe('Hosts View', function () { this.tags('includeFirefox'); + before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); + await Promise.all([ + esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + kibanaServer.savedObjects.cleanStandardList(), + ]); }); - describe('shows hosts view landing page for admin', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewBadge(); - }); - after(async () => { - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - }); + after(() => { + esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'); + esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); + }); - it('should show hosts landing page with enable button when the hosts view is disabled', async () => { - const landingPageEnableButton = - await pageObjects.infraHostsView.getHostsLandingPageEnableButton(); - const landingPageEnableButtonText = await landingPageEnableButton.getVisibleText(); - expect(landingPageEnableButtonText).to.eql('Enable hosts view'); - }); + it('should be accessible from the Inventory page', async () => { + await pageObjects.common.navigateToApp('infraOps'); + await pageObjects.infraHome.clickDismissKubernetesTourButton(); + await pageObjects.infraHostsView.clickTryHostViewBadge(); + + const pageUrl = await browser.getCurrentUrl(); + + expect(pageUrl).to.contain(HOSTS_VIEW_PATH); }); - describe('should show hosts view landing page for user with read permission', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await loginWithReadOnlyUserAndNavigateToInfra(); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewBadge(); - }); - after(async () => { - // NOTE: Logout needs to happen before anything else to avoid flaky behavior - await logoutAndDeleteReadOnlyUser(); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + describe('#Landing page', () => { + beforeEach(() => { + setHostViewEnabled(false); }); - it('should show hosts landing page with callout when the hosts view is disabled', async () => { + it('as a user with read permission, should show hosts landing page with callout when the hosts view is disabled', async () => { + await loginWithReadOnlyUser(); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + const landingPageDisabled = await pageObjects.infraHostsView.getHostsLandingPageDisabled(); const learnMoreDocsUrl = await pageObjects.infraHostsView.getHostsLandingPageDocsLink(); const parsedUrl = new URL(learnMoreDocsUrl); @@ -151,33 +126,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(landingPageDisabled).to.contain( 'Your user role doesn’t have sufficient privileges to enable this feature' ); + + await logoutAndDeleteReadOnlyUser(); + }); + + it('as an admin, should see an enable button when the hosts view is disabled', async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + + const landingPageEnableButton = + await pageObjects.infraHostsView.getHostsLandingPageEnableButton(); + const landingPageEnableButtonText = await landingPageEnableButton.getVisibleText(); + expect(landingPageEnableButtonText).to.eql('Enable hosts view'); + }); + + it('as an admin, should be able to enable the hosts view feature', async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await enableHostView(); + + const titleElement = await find.byCssSelector('h1'); + const title = await titleElement.getVisibleText(); + + expect(title).to.contain('Hosts'); }); }); - describe('enables hosts view page and checks content', () => { + describe('#Page Content', () => { before(async () => { - await navigateAndEnableHostView(); + await setHostViewEnabled(true); + await loginWithReadOnlyUser(); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.timePicker.setAbsoluteRange( START_DATE.format(timepickerFormat), END_DATE.format(timepickerFormat) ); }); + after(async () => { - await navigateAndDisableHostView(); + await logoutAndDeleteReadOnlyUser(); }); - describe('should show hosts page for admin user and see the page content', async () => { - it('should render the correct page title', async () => { - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); - }); + it('should render the correct page title', async () => { + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); + }); - it('should have six hosts', async () => { - const hosts = await pageObjects.infraHostsView.getHostsTableData(); - expect(hosts.length).to.equal(6); - }); + it('should render a table with 6 hosts', async () => { + const hosts = await pageObjects.infraHostsView.getHostsTableData(); + expect(hosts.length).to.equal(6); + }); - it('should load 5 metrics trend tiles', async () => { + describe('KPI tiles', () => { + it('should render 5 metrics trend tiles', async () => { const hosts = await pageObjects.infraHostsView.getAllMetricsTrendTiles(); expect(hosts.length).to.equal(5); }); @@ -195,56 +194,80 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); }); - }); - describe('should show hosts page for read only user and see the page content', async () => { - before(async () => { - await navigateAndEnableHostView(); - await loginWithReadOnlyUserAndNavigateToInfra(); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.infraHostsView.clickTryHostViewLink(); - await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(timepickerFormat), - END_DATE.format(timepickerFormat) - ); - }); - after(async () => { - // NOTE: Logout needs to happen before anything else to avoid flaky behavior - await logoutAndDeleteReadOnlyUser(); - await navigateAndDisableHostView(); - }); + describe('Metrics Tab', () => { + it('should load 8 lens metric charts', async () => { + const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); + expect(metricCharts.length).to.equal(8); + }); - it('should have six hosts', async () => { - const hosts = await pageObjects.infraHostsView.getHostsTableData(); - expect(hosts.length).to.equal(6); + it('should have an option to open the chart in lens', async () => { + await pageObjects.infraHostsView.getOpenInLensOption(); + }); }); - it('should load 5 metrics trend tiles', async () => { - const hosts = await pageObjects.infraHostsView.getAllMetricsTrendTiles(); - expect(hosts.length).to.equal(5); - }); + describe('Alerts Tab', () => { + const observability = getService('observability'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + const ACTIVE_ALERTS = 6; + const RECOVERED_ALERTS = 4; + const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; + const COLUMNS = 5; - [ - { metric: 'hosts', value: '6' }, - { metric: 'cpu', value: '0.8%' }, - { metric: 'memory', value: '16.8%' }, - { metric: 'tx', value: '0 bit/s' }, - { metric: 'rx', value: '0 bit/s' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - const tileValue = await pageObjects.infraHostsView.getMetricsTrendTileValue(metric); - expect(tileValue).to.eql(value); + before(async () => { + await pageObjects.infraHostsView.visitAlertTab(); }); - }); - describe('Lens charts', () => { - it('should load 8 lens metric charts', async () => { - const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); - expect(metricCharts.length).to.equal(8); + it('should correctly load the Alerts tab section when clicking on it', async () => { + testSubjects.existOrFail('hostsView-alerts'); }); - it('should have an option to open the chart in lens', async () => { - await pageObjects.infraHostsView.getOpenInLensOption(); + it('should correctly render a badge with the active alerts count', async () => { + const alertsCountBadge = await pageObjects.infraHostsView.getAlertsTabCountBadge(); + const alertsCount = await alertsCountBadge.getVisibleText(); + + expect(alertsCount).to.be('6'); + }); + + describe('#FilterButtonGroup', () => { + it('can be filtered to only show "active" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_ACTIVE); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ACTIVE_ALERTS); + }); + }); + + it('can be filtered to only show "recovered" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_RECOVERED); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(RECOVERED_ALERTS); + }); + }); + + it('can be filtered to only show "all" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ALL_ALERTS); + }); + }); + }); + + describe('#AlertsTable', () => { + it('should correctly render', async () => { + await observability.alerts.common.getTableOrFail(); + }); + + it('should renders the correct number of cells', async () => { + await retry.try(async () => { + const cells = await observability.alerts.common.getTableCells(); + expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + }); + }); }); }); }); diff --git a/x-pack/test/functional/apps/lens/group2/dashboard.ts b/x-pack/test/functional/apps/lens/group2/dashboard.ts index caa2dd3ca1785..6e4255808df8c 100644 --- a/x-pack/test/functional/apps/lens/group2/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group2/dashboard.ts @@ -28,6 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const panelActions = getService('dashboardPanelActions'); const inspector = getService('inspector'); + const queryBar = getService('queryBar'); async function clickInChart(x: number, y: number) { const el = await elasticChart.getCanvas(); @@ -294,5 +295,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.removeDimension('lnsXY_xDimensionPanel'); await PageObjects.lens.expectSaveAndReturnButtonDisabled(); }); + + it('should recover lens panel in an error state when fixing search query', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); + await find.clickByButtonText('lnsXYvis'); + await dashboardAddPanel.closeAddPanel(); + await PageObjects.lens.goToTimeRange(); + // type an invalid search query, hit refresh + await queryBar.setQuery('this is > not valid'); + await queryBar.submitQuery(); + // check the error state + await PageObjects.header.waitUntilLoadingHasFinished(); + const errors = await testSubjects.findAll('embeddableStackError'); + expect(errors.length).to.be(1); + // now remove the query + await queryBar.setQuery(''); + await queryBar.submitQuery(); + await PageObjects.header.waitUntilLoadingHasFinished(); + // check the success state + await PageObjects.dashboard.verifyNoRenderErrors(); + }); }); } diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 9c3bde2768827..f6930c8580732 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -31,7 +31,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('go to certs page', async () => { - await uptime.dismissTour(); await uptimeService.common.waitUntilDataIsLoaded(); await uptimeService.cert.hasViewCertButton(); await uptimeService.navigation.goToCertificates(); diff --git a/x-pack/test/functional/es_archives/infra/alerts/data.json.gz b/x-pack/test/functional/es_archives/infra/alerts/data.json.gz new file mode 100644 index 0000000000000..5410bb6abe5b0 Binary files /dev/null and b/x-pack/test/functional/es_archives/infra/alerts/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/alerts/mappings.json b/x-pack/test/functional/es_archives/infra/alerts/mappings.json new file mode 100644 index 0000000000000..89ab07bc009f3 --- /dev/null +++ b/x-pack/test/functional/es_archives/infra/alerts/mappings.json @@ -0,0 +1,776 @@ +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.apm.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.apm.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "processor": { + "properties": { + "event": { + "type": "keyword" + } + } + }, + "service": { + "properties": { + "environment": { + "type": "keyword" + }, + "name": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "transaction": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.apm.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.logs.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.logs.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.logs.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.metrics.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.metrics.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.metrics.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json b/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json index 42d23e794ba23..40408d65b6d89 100644 --- a/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json @@ -55,6 +55,24 @@ } } } + }, + "process.command_line": { + "type": "keyword", + "ignore_above": 10, + "fields": { + "text": { + "type": "text" + } + } + }, + "nonEcs.command_line": { + "type": "keyword", + "ignore_above": 10, + "fields": { + "text": { + "type": "text" + } + } } } }, diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index ddc7f24029d46..b070a1267d4f0 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AlertStatus, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../ftr_provider_context'; export function InfraHostsViewProvider({ getService }: FtrProviderContext) { @@ -79,5 +80,31 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail('embeddablePanelContextMenuOpen'); return testSubjects.existOrFail('embeddablePanelAction-openInLens'); }, + + // Alerts Tab + getAlertsTab() { + return testSubjects.find('hostsView-tabs-alerts'); + }, + + getAlertsTabCountBadge() { + return testSubjects.find('hostsView-tabs-alerts-count'); + }, + + async visitAlertTab() { + const alertsTab = await this.getAlertsTab(); + alertsTab.click(); + }, + + setAlertStatusFilter(alertStatus?: AlertStatus) { + const buttons = { + [ALERT_STATUS_ACTIVE]: 'hostsView-alert-status-filter-active-button', + [ALERT_STATUS_RECOVERED]: 'hostsView-alert-status-filter-recovered-button', + all: 'hostsView-alert-status-filter-show-all-button', + }; + + const buttonSubject = alertStatus ? buttons[alertStatus] : buttons.all; + + return testSubjects.click(buttonSubject); + }, }; } diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 315513c5c4c3b..531e0e5980255 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -16,7 +16,6 @@ export class UptimePageObject extends FtrService { private readonly monitor = this.ctx.getService('uptime').monitor; private readonly navigation = this.ctx.getService('uptime').navigation; private readonly retry = this.ctx.getService('retry'); - private readonly testSubjects = this.ctx.getService('testSubjects'); public async goToRoot(refresh?: boolean) { await this.navigation.goToUptime(); @@ -25,10 +24,6 @@ export class UptimePageObject extends FtrService { } } - public async dismissTour() { - await this.testSubjects.click('syntheticsManagementTourDismiss'); - } - public async setDateRange(start: string, end: string) { const { start: prevStart, end: prevEnd } = await this.timePicker.getTimeConfig(); if (start !== prevStart || prevEnd !== end) { @@ -44,10 +39,6 @@ export class UptimePageObject extends FtrService { monitorIdToCheck?: string ) { await this.navigation.goToUptime(); - const hasTour = await this.testSubjects.exists('syntheticsManagementTourDismiss'); - if (hasTour) { - await this.testSubjects.click('syntheticsManagementTourDismiss'); - } await this.setDateRange(dateStart, dateEnd); if (monitorIdToCheck) { await this.commonService.monitorIdExists(monitorIdToCheck); diff --git a/x-pack/test/functional/services/cases/list.ts b/x-pack/test/functional/services/cases/list.ts index ced5c5acedca2..492a25f1fa151 100644 --- a/x-pack/test/functional/services/cases/list.ts +++ b/x-pack/test/functional/services/cases/list.ts @@ -204,9 +204,10 @@ export function CasesTableServiceProvider( async changeStatus(status: CaseStatuses, index: number) { await this.openRowActions(index); - await testSubjects.existOrFail('cases-bulk-action-delete'); + await retry.waitFor('status panel exists', async () => { + return find.existsByCssSelector('[data-test-subj*="case-action-status-panel-"'); + }); - await find.existsByCssSelector('[data-test-subj*="case-action-status-panel-"'); const statusButton = await find.byCssSelector('[data-test-subj*="case-action-status-panel-"'); statusButton.click(); @@ -218,9 +219,10 @@ export function CasesTableServiceProvider( async changeSeverity(severity: CaseSeverity, index: number) { await this.openRowActions(index); - await testSubjects.existOrFail('cases-bulk-action-delete'); + await retry.waitFor('severity panel exists', async () => { + return find.existsByCssSelector('[data-test-subj*="case-action-severity-panel-"'); + }); - await find.existsByCssSelector('[data-test-subj*="case-action-severity-panel-"'); const statusButton = await find.byCssSelector( '[data-test-subj*="case-action-severity-panel-"' ); diff --git a/x-pack/test/functional/services/rules/api.ts b/x-pack/test/functional/services/rules/api.ts index e4952adb1e473..0fdb40cc918a3 100644 --- a/x-pack/test/functional/services/rules/api.ts +++ b/x-pack/test/functional/services/rules/api.ts @@ -60,7 +60,7 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); - for (const rule of body) { + for (const rule of body.data) { await this.deleteRule(rule.id); } }, diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index b45f12eaee9be..57e39f6bf9d0e 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -82,10 +82,6 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { - const hasTour = await testSubjects.exists('syntheticsManagementTourDismiss'); - if (hasTour) { - await testSubjects.click('syntheticsManagementTourDismiss'); - } await PageObjects.timePicker.setAbsoluteRange(dateStart, dateEnd); await this.goToMonitor(monitorId); }, diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts index 485f99f4db94a..b7b5d892681c0 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts @@ -590,9 +590,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/148468 - // FLAKY: https://github.com/elastic/kibana/issues/148469 - describe.skip('Severity', () => { + describe('Severity', () => { before(async () => { await cases.api.createNthRandomCases(1); await header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts index 85e118756c4b7..32f0e2ea49f92 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile, getService }: FtrProviderContext) { const browser = getService('browser'); const actions = getService('actions'); + const rules = getService('rules'); describe('stack alerting', function () { before(async () => { @@ -23,10 +24,12 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { }); after(async () => { + await rules.api.deleteAllRules(); await actions.api.deleteAllConnectors(); }); loadTestFile(require.resolve('./list_view')); loadTestFile(require.resolve('./connector_types')); + loadTestFile(require.resolve('./index_threshold_rule')); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts new file mode 100644 index 0000000000000..ec27e67f0874e --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts @@ -0,0 +1,141 @@ +/* + * 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 ({ getService, getPageObjects }: FtrProviderContext) { + const comboBox = getService('comboBox'); + const commonScreenshots = getService('commonScreenshots'); + const find = getService('find'); + const rules = getService('rules'); + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'header']); + const screenshotDirectories = ['response_ops_docs', 'stack_alerting']; + + describe('index threshold rule', function () { + it('create rule screenshot', async () => { + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await rules.common.clickCreateAlertButton(); + await testSubjects.setValue('ruleNameInput', 'kibana sites - high egress'); + await testSubjects.click('tagsComboBox'); + await testSubjects.setValue('tagsComboBox', 'sample-data'); + await testSubjects.click('solutionsFilterButton'); + await testSubjects.click('solutionstackAlertsFilterOption'); + await testSubjects.setValue('solutionsFilterButton', 'solutionstackAlertsFilterOption'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-select', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.click('.index-threshold-SelectOption'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-conditions', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.scrollIntoView('selectIndexExpression'); + await testSubjects.click('selectIndexExpression'); + const indexComboBox = await find.byCssSelector('#indexSelectSearchBox'); + await indexComboBox.click(); + await indexComboBox.type('kibana_sample_data_logs '); + const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); + await filterSelectItem.click(); + await testSubjects.click('thresholdAlertTimeFieldSelect'); + await testSubjects.setValue('thresholdAlertTimeFieldSelect', '@timestamp'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-index', + screenshotDirectories, + 1400, + 1024 + ); + await testSubjects.click('closePopover'); + + await testSubjects.click('whenExpression'); + await testSubjects.click('whenExpressionSelect'); + await testSubjects.setValue('whenExpressionSelect', 'sum()'); + await testSubjects.click('ofExpressionPopover'); + const ofComboBox = await find.byCssSelector('#ofField'); + await ofComboBox.click(); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-aggregation', + screenshotDirectories, + 1400, + 1024 + ); + await ofComboBox.type('bytes'); + const ofOptionsString = await comboBox.getOptionsList('availablefieldsOptionsComboBox'); + const ofOptions = ofOptionsString.trim().split('\n'); + expect(ofOptions.length > 0).to.be(true); + await comboBox.set('availablefieldsOptionsComboBox', ofOptions[0]); + + await testSubjects.click('groupByExpression'); + await testSubjects.click('overExpressionSelect'); + await testSubjects.setValue('overExpressionSelect', 'top'); + await testSubjects.setValue('fieldsNumberSelect', '4'); + await testSubjects.setValue('fieldsExpressionSelect', 'host.keyword'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-grouping', + screenshotDirectories, + 1400, + 1024 + ); + // need this two out of popup clicks to close them + const nameInput1 = await testSubjects.find('ruleNameInput'); + await nameInput1.click(); + + await testSubjects.click('thresholdPopover'); + await testSubjects.setValue('alertThresholdInput', '420000'); + await testSubjects.click('forLastExpression'); + await testSubjects.setValue('timeWindowSizeNumber', '24'); + await testSubjects.setValue('timeWindowUnitSelect', 'hours'); + // need this two out of popup clicks to close them + const nameInput2 = await testSubjects.find('ruleNameInput'); + await nameInput2.click(); + await testSubjects.scrollIntoView('thresholdPopover'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-threshold', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.setValue('intervalInput', '4'); + await testSubjects.setValue('intervalInputUnit', 'hours'); + // need this two out of popup clicks to close them + const nameInput3 = await testSubjects.find('ruleNameInput'); + await nameInput3.click(); + await testSubjects.scrollIntoView('alertVisualizationChart'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-preview', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.click('.server-log-alerting-ActionTypeSelectOption'); + await testSubjects.scrollIntoView('addAlertActionButton'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-action', + screenshotDirectories, + 1400, + 1024 + ); + /* + * const saveButton = await testSubjects.find('saveRuleButton'); + * await saveButton.click(); + */ + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 589a61295a541..6aab2457c5278 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -15,8 +15,7 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; - // FLAKY: https://github.com/elastic/kibana/issues/72874 - describe.skip('endpoint', function () { + describe('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/stack_functional_integration/apps/heartbeat/_heartbeat.ts b/x-pack/test/stack_functional_integration/apps/heartbeat/_heartbeat.ts index e1122125c8665..9f402c019d0a7 100644 --- a/x-pack/test/stack_functional_integration/apps/heartbeat/_heartbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/heartbeat/_heartbeat.ts @@ -15,7 +15,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('check heartbeat overview page', function () { it('Uptime app should show 1 UP monitor', async function () { await PageObjects.common.navigateToApp('uptime', { insertTimestamp: false }); - await PageObjects.uptime.dismissTour(); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index be736ec73fd0d..bf4fe75140b6f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -117,5 +117,8 @@ "@kbn/security-api-integration-helpers", "@kbn/alerts-as-data-utils", "@kbn/discover-plugin", + "@kbn/files-plugin", + "@kbn/shared-ux-file-types", + "@kbn/securitysolution-io-ts-alerting-types", ] } diff --git a/yarn.lock b/yarn.lock index 822dedf6afcd4..5419888e4c49a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,7 +393,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.1", "@babel/parser@^7.21.2": +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== @@ -8005,7 +8005,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.50": +"@types/estree@*": version "0.0.50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== @@ -8015,6 +8015,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + "@types/expect@^1.20.4": version "1.20.4" resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" @@ -13019,10 +13024,10 @@ d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0, d3-array@^1.2.4: resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== -"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.1.1.tgz#7797eb53ead6b9083c75a45a681e93fc41bc468c" - integrity sha512-33qQ+ZoZlli19IFiQx4QEpf2CBEayMRzhlisJHSCsSUbDXv6ZishqS1x7uFVClKG4Wr7rZVHvaAttoLow6GqdQ== +"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3.2.2, d3-array@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.2.tgz#f8ac4705c5b06914a7e0025bbf8d5f1513f6a86e" + integrity sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ== dependencies: internmap "1 - 2" @@ -13066,7 +13071,7 @@ d3-color@1, d3-color@^1.0.3: resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== -"d3-color@1 - 3", d3-color@^3.0.1: +"d3-color@1 - 3", d3-color@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== @@ -13154,10 +13159,10 @@ d3-geo-projection@^4.0.0: d3-array "1 - 3" d3-geo "1.12.0 - 3" -"d3-geo@1.12.0 - 3", d3-geo@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" - integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== +"d3-geo@1.12.0 - 3", d3-geo@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e" + integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA== dependencies: d3-array "2.5.0 - 3" @@ -13173,10 +13178,10 @@ d3-hierarchy@^1.1.4: resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== -d3-hierarchy@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz#9cbb0ffd2375137a351e6cfeed344a06d4ff4597" - integrity sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA== +d3-hierarchy@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== d3-interpolate@1, d3-interpolate@^1.1.4: version "1.4.0" @@ -13209,10 +13214,10 @@ d3-path@1: resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== -"d3-path@1 - 3", d3-path@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" - integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== "d3-quadtree@1 - 3": version "2.0.0" @@ -13282,12 +13287,12 @@ d3-shape@^2.0.0, d3-shape@^2.1.0: dependencies: d3-path "1 - 2" -d3-shape@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" - integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== +d3-shape@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: - d3-path "1 - 3" + d3-path "^3.1.0" d3-time-format@2: version "2.2.3" @@ -13322,10 +13327,10 @@ d3-time@1: dependencies: d3-array "2" -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" - integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" @@ -23988,10 +23993,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.43.1: - version "7.43.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.1.tgz#0d0d7822f3f7fc05ffc41d5f012b49b90fcfa0f0" - integrity sha512-+s3+s8LLytRMriwwuSqeLStVjRXFGxgjjx2jED7Z+wz1J/88vpxieRQGvJVvzrzVxshZ0BRuocFERb779m2kNg== +react-hook-form@^7.43.2: + version "7.43.2" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.2.tgz#d8ff71956dc3de258dce19d4b1c7e1c6a0188e67" + integrity sha512-NvD3Oe2Y9hhqo2R4I4iJigDzSLpdMnzUpNMxlnzTbdiT7NT3BW0GxWCzEtwPudZMUPbZhNcSy1EcGAygyhDORg== react-input-autosize@^3.0.0: version "3.0.0" @@ -27130,7 +27135,7 @@ terser@^4.1.2, terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.14.1, terser@^5.16.4, terser@^5.3.4, terser@^5.9.0: +terser@^5.14.1, terser@^5.16.5, terser@^5.3.4, terser@^5.9.0: version "5.16.5" resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.5.tgz#1c285ca0655f467f92af1bbab46ab72d1cb08e5a" integrity sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg== @@ -27766,9 +27771,9 @@ unc-path-regex@^0.1.2: integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= undici@^5.11.0, undici@^5.5.1: - version "5.14.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.14.0.tgz#1169d0cdee06a4ffdd30810f6228d57998884d00" - integrity sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ== + version "5.20.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.20.0.tgz#6327462f5ce1d3646bcdac99da7317f455bcc263" + integrity sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g== dependencies: busboy "^1.6.0" @@ -28334,122 +28339,122 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vega-canvas@^1.2.5, vega-canvas@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.2.6.tgz#55e032ce9a62acd17229f6bac66d99db3d6879cd" - integrity sha512-rgeYUpslYn/amIfnuv3Sw6n4BGns94OjjZNtUc9IDji6b+K8LGS/kW+Lvay8JX/oFqtulBp8RLcHN6QjqPLA9Q== +vega-canvas@^1.2.6, vega-canvas@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.2.7.tgz#cf62169518f5dcd91d24ad352998c2248f8974fb" + integrity sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q== -vega-crossfilter@~4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.0.tgz#b6c5a728ce987f2514074adb22cf86b9bc63e0c8" - integrity sha512-aiOJcvVpiEDIu5uNc4Kf1hakkkPaVOO5fw5T4RSFAw6GEDbdqcB6eZ1xePcsLVic1hxYD5SGiUPdiiIs0SMh2g== +vega-crossfilter@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.1.tgz#3ff3ca0574883706f7a399dc6d60f4a0f065ece4" + integrity sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q== dependencies: - d3-array "^3.1.1" - vega-dataflow "^5.7.3" - vega-util "^1.15.2" + d3-array "^3.2.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-dataflow@^5.7.3, vega-dataflow@^5.7.4, vega-dataflow@~5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.4.tgz#7cafc0a41b9d0b11dd2e34a513f8b7ca345dfd74" - integrity sha512-JGHTpUo8XGETH3b1V892we6hdjzCWB977ybycIu8DPqRoyrZuj6t1fCVImazfMgQD1LAfJlQybWP+alwKDpKig== +vega-dataflow@^5.7.3, vega-dataflow@^5.7.5, vega-dataflow@~5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.5.tgz#0d559f3c3a968831f2995e099a2e270993ddfed9" + integrity sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA== dependencies: - vega-format "^1.0.4" - vega-loader "^4.3.2" - vega-util "^1.16.1" + vega-format "^1.1.1" + vega-loader "^4.5.1" + vega-util "^1.17.1" -vega-encode@~4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.9.0.tgz#3dd1031056bb8029f262afc4b4d58372c8f073a6" - integrity sha512-etv2BHuCn9bzEc0cxyA2TnbtcAFQGVFmsaqmB4sgBCaqTSEfXMoX68LK3yxBrsdm5LU+y3otJVoewi3qWYCx2g== +vega-encode@~4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.9.1.tgz#bad0e99bebec86d42184bcb898576c8accd91e89" + integrity sha512-05JB47UZaqIBS9t6rtHI/aKjEuH4EsSIH+wJWItht4BFj33eIl4XRNtlXdE31uuQT2pXWz5ZWW3KboMuaFzKLw== dependencies: - d3-array "^3.1.1" + d3-array "^3.2.2" d3-interpolate "^3.0.1" - vega-dataflow "^5.7.3" - vega-scale "^7.0.3" - vega-util "^1.15.2" + vega-dataflow "^5.7.5" + vega-scale "^7.3.0" + vega-util "^1.17.1" -vega-event-selector@^3.0.0, vega-event-selector@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.0.tgz#7b855ac0c3ddb59bc5b5caa0d96dbbc9fbd33a4c" - integrity sha512-Gls93/+7tEJGE3kUuUnxrBIxtvaNeF01VIFB2Q2Of2hBIBvtHX74jcAdDtkh5UhhoYGD8Q1J30P5cqEBEwtPoQ== +vega-event-selector@^3.0.1, vega-event-selector@~3.0.0, vega-event-selector@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.1.tgz#b99e92147b338158f8079d81b28b2e7199c2e259" + integrity sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A== -vega-expression@^5.0.0, vega-expression@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.0.0.tgz#938f26689693a1e0d26716030cdaed43ca7abdfb" - integrity sha512-y5+c2frq0tGwJ7vYXzZcfVcIRF/QGfhf2e+bV1Z0iQs+M2lI1II1GPDdmOcMKimpoCVp/D61KUJDIGE1DSmk2w== +vega-expression@^5.0.1, vega-expression@~5.0.0, vega-expression@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.0.1.tgz#e6a6eff564d2a93496a9bf34cbc78d8942f236a8" + integrity sha512-atfzrMekrcsuyUgZCMklI5ki8cV763aeo1Y6YrfYU7FBwcQEoFhIV/KAJ1vae51aPDGtfzvwbtVIo3WShFCP2Q== dependencies: - "@types/estree" "^0.0.50" - vega-util "^1.16.0" + "@types/estree" "^1.0.0" + vega-util "^1.17.1" -vega-force@~4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.1.0.tgz#cc8dea972baa52adc60840ff744ebb9e57d8f1f5" - integrity sha512-Sssf8iH48vYlz+E7/RpU+SUaJbuLoIL87U4tG2Av4gf/hRiImU49x2TI3EuhFWg1zpaCFxlz0CAaX++Oh/gjdw== +vega-force@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.1.1.tgz#27bffa96bda293f27d2a2492c2cbf99d49fec77e" + integrity sha512-T6fJAUz9zdXf2qj2Hz0VlmdtaY7eZfcKNazhUV8hza4R3F9ug6r/hSrdovfc9ExmbUjL5iyvDUsf63r8K3/wVQ== dependencies: d3-force "^3.0.0" - vega-dataflow "^5.7.3" - vega-util "^1.15.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-format@^1.0.4, vega-format@^1.1.0, vega-format@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.0.tgz#b9d81cf1bcf222ae5cbd94357ae70245d2c7b18d" - integrity sha512-6mgpeWw8yGdG0Zdi8aVkx5oUrpJGOpNxqazC2858RSDPvChM/jDFlgRMTYw52qk7cxU0L08ARp4BwmXaI75j0w== +vega-format@^1.1.1, vega-format@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.1.tgz#92e4876e18064e7ad54f39045f7b24dede0030b8" + integrity sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ== dependencies: - d3-array "^3.1.1" + d3-array "^3.2.2" d3-format "^3.1.0" d3-time-format "^4.1.0" - vega-time "^2.0.3" - vega-util "^1.15.2" - -vega-functions@^5.12.1, vega-functions@^5.13.0, vega-functions@~5.13.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.13.0.tgz#c9ab8c6eedbf39f75b424cca6776b1d0b8c74b32" - integrity sha512-Mf53zNyx+c9fFqagEI0T8zc9nMlx0zozOngr8oOpG1tZDKOgwOnUgN99zQKbLHjyv+UzWrq3LYTnSLyVe0ZmhQ== - dependencies: - d3-array "^3.1.1" - d3-color "^3.0.1" - d3-geo "^3.0.1" - vega-dataflow "^5.7.3" - vega-expression "^5.0.0" - vega-scale "^7.2.0" - vega-scenegraph "^4.9.3" - vega-selections "^5.3.1" - vega-statistics "^1.7.9" - vega-time "^2.1.0" - vega-util "^1.16.0" - -vega-geo@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.0.tgz#ce792df57f8ca4c54a7a1a29467cfa24bc53f573" - integrity sha512-3YX41y+J5pu0PMjvBCASg0/lgvu9+QXWJZ+vl6FFKa8AlsIopQ67ZL7ObwqjZcoZMolJ4q0rc+ZO8aj1pXCYcw== - dependencies: - d3-array "^3.1.1" - d3-color "^3.0.1" - d3-geo "^3.0.1" - vega-canvas "^1.2.5" - vega-dataflow "^5.7.3" - vega-projection "^1.4.5" - vega-statistics "^1.7.9" - vega-util "^1.15.2" - -vega-hierarchy@~4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.0.tgz#605edbe3a6232853f9e8b57e3b905471d33b1a48" - integrity sha512-DWBK39IEt4FiQru12twzKSFUvFFZ7KtlH9+lAaqrJnKuIZFCyQ1XOUfKScfbKIlk4KS+DuCTNLI/pxC/f7Sk9Q== + vega-time "^2.1.1" + vega-util "^1.17.1" + +vega-functions@^5.13.1, vega-functions@~5.13.1: + version "5.13.1" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.13.1.tgz#504d672924495fe3ea844e6940c7f6e151cde151" + integrity sha512-0LhntimnvBl4VzRO/nkCwCTbtaP8bE552galKQbCg88GDxdmcmlsoTCwUzG0vZ/qmNM3IbqnP5k5/um3zwFqLw== + dependencies: + d3-array "^3.2.2" + d3-color "^3.1.0" + d3-geo "^3.1.0" + vega-dataflow "^5.7.5" + vega-expression "^5.0.1" + vega-scale "^7.3.0" + vega-scenegraph "^4.10.2" + vega-selections "^5.4.1" + vega-statistics "^1.8.1" + vega-time "^2.1.1" + vega-util "^1.17.1" + +vega-geo@~4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.1.tgz#3850232bf28c98fab5e26c5fb401acb6fb37b5e5" + integrity sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA== + dependencies: + d3-array "^3.2.2" + d3-color "^3.1.0" + d3-geo "^3.1.0" + vega-canvas "^1.2.7" + vega-dataflow "^5.7.5" + vega-projection "^1.6.0" + vega-statistics "^1.8.1" + vega-util "^1.17.1" + +vega-hierarchy@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.1.tgz#897974a477dfa70cc0d4efab9465b6cc79a9071f" + integrity sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ== dependencies: - d3-hierarchy "^3.1.0" - vega-dataflow "^5.7.3" - vega-util "^1.15.2" + d3-hierarchy "^3.1.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" vega-interpreter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vega-interpreter/-/vega-interpreter-1.0.4.tgz#291ebf85bc2d1c3550a3da22ff75b3ba0d326a39" integrity sha512-6tpYIa/pJz0cZo5fSxDSkZkAA51pID2LjOtQkOQvbzn+sJiCaWKPFhur8MBqbcmYZ9bnap1OYNwlrvpd2qBLvg== -vega-label@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.2.0.tgz#bcb2659aec54f890f9debab3e41ab87a58292dce" - integrity sha512-1prOqkCAfXaUvMqavbGI0nbYGqV8UQR9qvuVwrPJ6Yxm3GIUIOA/JRqNY8eZR8USwMP/kzsqlfVEixj9+Y75VQ== +vega-label@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.2.1.tgz#ea45fa5a407991c44edfea9c4ca40874d544a3db" + integrity sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg== dependencies: vega-canvas "^1.2.6" vega-dataflow "^5.7.3" @@ -28473,110 +28478,112 @@ vega-lite@^5.5.0: vega-util "~1.17.0" yargs "~17.5.1" -vega-loader@^4.3.2, vega-loader@^4.4.0, vega-loader@~4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.0.tgz#b15acc4c8f84191f500e94d35ddfb9721ac943ad" - integrity sha512-EkAyzbx0pCYxH3v3wghGVCaKINWxHfgbQ2pYDiYv0yo8e04S8Mv/IlRGTt6BAe7cLhrk1WZ4zh20QOppnGG05w== +vega-loader@^4.5.1, vega-loader@~4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.1.tgz#b85262b3cb8376487db0c014a8a13c3a5e6d52ad" + integrity sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA== dependencies: d3-dsv "^3.0.1" node-fetch "^2.6.7" topojson-client "^3.1.0" - vega-format "^1.1.0" - vega-util "^1.16.0" + vega-format "^1.1.1" + vega-util "^1.17.1" -vega-parser@~6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.4.tgz#4868e41af2c9645b6d7daeeb205cfad06b9d465c" - integrity sha512-tORdpWXiH/kkXcpNdbSVEvtaxBuuDtgYp9rBunVW9oLsjFvFXbSWlM1wvJ9ZFSaTfx6CqyTyGMiJemmr1QnTjQ== +vega-parser@~6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.2.0.tgz#c982aff0a6409486cbbe743a5799412b8b897654" + integrity sha512-as+QnX8Qxe9q51L1C2sVBd+YYYctP848+zEvkBT2jlI2g30aZ6Uv7sKsq7QTL6DUbhXQKR0XQtzlanckSFdaOQ== dependencies: - vega-dataflow "^5.7.3" - vega-event-selector "^3.0.0" - vega-functions "^5.12.1" - vega-scale "^7.1.1" - vega-util "^1.16.0" + vega-dataflow "^5.7.5" + vega-event-selector "^3.0.1" + vega-functions "^5.13.1" + vega-scale "^7.3.0" + vega-util "^1.17.1" -vega-projection@^1.4.5, vega-projection@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.5.0.tgz#51c5f0455170cd35b3c5f3e653e99c3616520640" - integrity sha512-aob7qojh555x3hQWZ/tr8cIJNSWQbm6EoWTJaheZgFOY2x3cDa4Qrg3RJbGw6KwVj/IQk2p40paRzixKZ2kr+A== +vega-projection@^1.6.0, vega-projection@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.0.tgz#921acd3220e7d9d04ccd5ce0109433afb3236966" + integrity sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ== dependencies: - d3-geo "^3.0.1" + d3-geo "^3.1.0" d3-geo-projection "^4.0.0" + vega-scale "^7.3.0" -vega-regression@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.1.0.tgz#b4394db403ada93de52bb4536d04be336c983a8c" - integrity sha512-09K0RemY6cdaXBAyakDUNFfEkRcLkGjkDJyWQPAUqGK59hV2J+G3i4uxkZp18Vu0t8oqU7CgzwWim1s5uEpOcA== +vega-regression@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.1.1.tgz#b53a964152a2fec4847e31571f522bfda23089af" + integrity sha512-98i/z0vdDhOIEhJUdYoJ2nlfVdaHVp2CKB39Qa7G/XyRw0+QwDFFrp8ZRec2xHjHfb6bYLGNeh1pOsC13FelJg== dependencies: - d3-array "^3.1.1" + d3-array "^3.2.2" vega-dataflow "^5.7.3" vega-statistics "^1.7.9" vega-util "^1.15.2" -vega-runtime@^6.1.3, vega-runtime@~6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.1.3.tgz#01e18246f7a80cee034a96017ac30113b92c4034" - integrity sha512-gE+sO2IfxMUpV0RkFeQVnHdmPy3K7LjHakISZgUGsDI/ZFs9y+HhBf8KTGSL5pcZPtQsZh3GBQ0UonqL1mp9PA== +vega-runtime@^6.1.4, vega-runtime@~6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.1.4.tgz#98b67160cea9554e690bfd44719f9d17f90c4220" + integrity sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ== dependencies: - vega-dataflow "^5.7.3" - vega-util "^1.15.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-scale@^7.0.3, vega-scale@^7.1.1, vega-scale@^7.2.0, vega-scale@~7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.2.0.tgz#9e298cc02ad340498cb56847436b19439911f0fc" - integrity sha512-QYltO/otrZHLrCGGf06Y99XtPtqWXITr6rw7rO9oL+l3d9o5RFl9sjHrVxiM7v+vGoZVWbBd5IPbFhPsXZ6+TA== +vega-scale@^7.3.0, vega-scale@~7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.3.0.tgz#02b83435a892c6d91a87ee7d3d350fac987f464b" + integrity sha512-pMOAI2h+e1z7lsqKG+gMfR6NKN2sTcyjZbdJwntooW0uFHwjLGjMSY7kSd3nSEquF0HQ8qF7zR6gs1eRwlGimw== dependencies: - d3-array "^3.1.1" + d3-array "^3.2.2" d3-interpolate "^3.0.1" d3-scale "^4.0.2" - vega-time "^2.1.0" - vega-util "^1.17.0" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-scenegraph@^4.10.0, vega-scenegraph@^4.9.2, vega-scenegraph@^4.9.3, vega-scenegraph@~4.10.1: - version "4.10.1" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.10.1.tgz#944da67b8a28758fab2e1306259fb7ff6be89f6b" - integrity sha512-takIpkmNxYHhJYALOYzhTin3EDzbys6U4g+l1yJZVlXG9YTdiCMuEVAdtaQOCqF9/7qytD6pCrMxJY2HaoN0qQ== - dependencies: - d3-path "^3.0.1" - d3-shape "^3.1.0" - vega-canvas "^1.2.5" - vega-loader "^4.4.0" - vega-scale "^7.2.0" - vega-util "^1.15.2" +vega-scenegraph@^4.10.2, vega-scenegraph@^4.9.2, vega-scenegraph@~4.10.2: + version "4.10.2" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.10.2.tgz#3ae9ad8e99bbf75e2a4f3ebf2c1f9dee7562d245" + integrity sha512-R8m6voDZO5+etwNMcXf45afVM3XAtokMqxuDyddRl9l1YqSJfS+3u8hpolJ50c2q6ZN20BQiJwKT1o0bB7vKkA== + dependencies: + d3-path "^3.1.0" + d3-shape "^3.2.0" + vega-canvas "^1.2.7" + vega-loader "^4.5.1" + vega-scale "^7.3.0" + vega-util "^1.17.1" vega-schema-url-parser@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.2.0.tgz#a0d1e02915adfbfcb1fd517c8c2ebe2419985c1e" integrity sha512-yAtdBnfYOhECv9YC70H2gEiqfIbVkq09aaE4y/9V/ovEFmH9gPKaEgzIZqgT7PSPQjKhsNkb6jk6XvSoboxOBw== -vega-selections@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.3.1.tgz#af5c3cc6532a55a5b692eb0fcc2a1d8d521605a4" - integrity sha512-cm4Srw1WHjcLGXX7GpxiUlfESv8XPu5b6Vh3mqMDPU94P2FO91SR9gei+EtRdt+KCFgIjr//MnRUjg/hAWwjkQ== +vega-selections@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.4.1.tgz#3233acb920703bfc323df8b960aa52e55ac08c70" + integrity sha512-EtYc4DvA+wXqBg9tq+kDomSoVUPCmQfS7hUxy2qskXEed79YTimt3Hcl1e1fW226I4AVDBEqTTKebmKMzbSgAA== dependencies: - vega-expression "^5.0.0" - vega-util "^1.16.0" + d3-array "3.2.2" + vega-expression "^5.0.1" + vega-util "^1.17.1" vega-spec-injector@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/vega-spec-injector/-/vega-spec-injector-0.0.2.tgz#f1d990109dd9d845c524738f818baa4b72a60ca6" integrity sha512-wOMMqmpssn0/ZFPW7wl1v26vbseRX7zHPWzEyS9TwNXTRCu1TcjIBIR+X23lCWocxhoBqFxmqyn8UowMhlGtAg== -vega-statistics@^1.7.9, vega-statistics@^1.8.0, vega-statistics@~1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.8.0.tgz#ad66f7461473d58bc96671588981a059ffd60b59" - integrity sha512-dl+LCRS6qS4jWDme/NEdPVt5r649uB4IK6Kyr2/czmGA5JqjuFmtQ9lHQOnRu8945XLkqLf+JIQQo7vnw+nslA== +vega-statistics@^1.7.9, vega-statistics@^1.8.1, vega-statistics@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.8.1.tgz#596fc3713ac68cc649bf28d0faf7def5ef34fef6" + integrity sha512-eRR3LZBusnTXUkc/uunAvWi1PjCJK+Ba4vFvEISc5Iv5xF4Aw2cBhEz1obEt6ID5fGVCTAl0E1LOSFxubS89hQ== dependencies: - d3-array "^3.1.1" + d3-array "^3.2.2" -vega-time@^2.0.3, vega-time@^2.1.0, vega-time@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.0.tgz#acfbab88d7798b87ff63913b0dce2ca5eb0d46ca" - integrity sha512-Q9/l3S6Br1RPX5HZvyLD/cQ4K6K8DtpR09/1y7D66gxNorg2+HGzYZINH9nUvN3mxoXcBWg4cCUh3+JvmkDaEg== +vega-time@^2.1.1, vega-time@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.1.tgz#0f1fb4e220dd5ed57401b58fb2293241f049ada0" + integrity sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA== dependencies: - d3-array "^3.1.1" - d3-time "^3.0.0" - vega-util "^1.15.2" + d3-array "^3.2.2" + d3-time "^3.1.0" + vega-util "^1.17.1" vega-tooltip@^0.28.0: version "0.28.0" @@ -28585,106 +28592,107 @@ vega-tooltip@^0.28.0: dependencies: vega-util "^1.17.0" -vega-transforms@~4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.10.0.tgz#a1017ede13cf4e25499f588610a3be4da615d82d" - integrity sha512-Yk6ByzVq5F2niFfPlSsrU5wi+NZhsF7IBpJCcTfms4U7eoyNepUXagdFEJ3VWBD/Lit6GorLXFgO17NYcyS5gg== - dependencies: - d3-array "^3.1.1" - vega-dataflow "^5.7.4" - vega-statistics "^1.8.0" - vega-time "^2.1.0" - vega-util "^1.16.1" - -vega-typings@~0.22.0: - version "0.22.1" - resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.22.1.tgz#287c646cfa93b1822d0fb6ea11d5543632f8b56e" - integrity sha512-88cIrjmoTxo/0nWTf+GuitkFhirHWVWCfymADiCUXt6s9arpQ6XPP5xjrN5KDc0LZd9xr7p4FIiEgADghgLTgw== - dependencies: - vega-event-selector "^3.0.0" - vega-expression "^5.0.0" - vega-util "^1.15.2" - -vega-util@^1.15.2, vega-util@^1.16.0, vega-util@^1.16.1, vega-util@^1.17.0, vega-util@~1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.0.tgz#b72ae0baa97f943bf591f8f5bb27ceadf06834ac" - integrity sha512-HTaydZd9De3yf+8jH66zL4dXJ1d1p5OIFyoBzFiOli4IJbwkL1jrefCKz6AHDm1kYBzDJ0X4bN+CzZSCTvNk1w== - -vega-view-transforms@~4.5.8: - version "4.5.8" - resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.5.8.tgz#c8dc42c3c7d4aa725d40b8775180c9f23bc98f4e" - integrity sha512-966m7zbzvItBL8rwmF2nKG14rBp7q+3sLCKWeMSUrxoG+M15Smg5gWEGgwTG3A/RwzrZ7rDX5M1sRaAngRH25g== +vega-transforms@~4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.10.1.tgz#5e51f4f3a745d43609e0d8ba1d74a7e53014030a" + integrity sha512-0uWrUZaYl8kjWrGbvPOQSKk6kcNXQFY9moME+bUmkADAvFptphCGbaEIn/nSsG6uCxj8E3rqKmKfjSWdU5yOqA== dependencies: - vega-dataflow "^5.7.3" - vega-scenegraph "^4.9.2" - vega-util "^1.15.2" + d3-array "^3.2.2" + vega-dataflow "^5.7.5" + vega-statistics "^1.8.1" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-view@~5.11.0: - version "5.11.0" - resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.11.0.tgz#8a7b29a36776e43cc6599e087ed7f48a918b805d" - integrity sha512-MI9NTRFmtFX6ADk6KOHhi8bhHjC9pPm42Bj2+74c6l1d3NQZf9Jv7lkiGqKohdkQDNH9LPwz/6slhKwPU9JdkQ== +vega-typings@~0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.23.0.tgz#5b001f5b51a477e67d2446ef9b964e1dac48a20e" + integrity sha512-10ZRRGoUZoQLS5jMiIFhSZMDc3UkPhDP2VMUN/oHZXElvPCGjfjvgmiC6XzvvN4sRXdccMcZX1lZPoyYPERVkA== dependencies: - d3-array "^3.1.1" + "@types/geojson" "^7946.0.10" + vega-event-selector "^3.0.1" + vega-expression "^5.0.1" + vega-util "^1.17.1" + +vega-util@^1.15.2, vega-util@^1.17.0, vega-util@^1.17.1, vega-util@~1.17.0, vega-util@~1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.1.tgz#717865fc6b660ceb3ae16273d21166ed471c2db4" + integrity sha512-ToPkWoBdP6awoK+bnYaFhgdqZhsNwKxWbuMnFell+4K/Cb6Q1st5Pi9I7iI5Y6n5ZICDDsd6eL7/IhBjEg1NUQ== + +vega-view-transforms@~4.5.9: + version "4.5.9" + resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz#5f109555c08ee9ac23ff9183d578eb9cbac6fe61" + integrity sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g== + dependencies: + vega-dataflow "^5.7.5" + vega-scenegraph "^4.10.2" + vega-util "^1.17.1" + +vega-view@~5.11.1: + version "5.11.1" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.11.1.tgz#a703d7d6344489c6a6e9e9d9c7a732519bf4432c" + integrity sha512-RoWxuoEMI7xVQJhPqNeLEHCezudsf3QkVMhH5tCovBqwBADQGqq9iWyax3ZzdyX1+P3eBgm7cnLvpqtN2hU8kA== + dependencies: + d3-array "^3.2.2" d3-timer "^3.0.1" - vega-dataflow "^5.7.3" - vega-format "^1.1.0" - vega-functions "^5.13.0" - vega-runtime "^6.1.3" - vega-scenegraph "^4.10.0" - vega-util "^1.16.1" - -vega-voronoi@~4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.0.tgz#14c74c84f52d9a16be2facd1bede879d32d988f2" - integrity sha512-1iuNAVZgUHRlBpdq4gSga3KlQmrgFfwy+KpyDgPLQ8HbLkhcVeT7RDh2L6naluqD7Op0xVLms3clR920WsYryQ== + vega-dataflow "^5.7.5" + vega-format "^1.1.1" + vega-functions "^5.13.1" + vega-runtime "^6.1.4" + vega-scenegraph "^4.10.2" + vega-util "^1.17.1" + +vega-voronoi@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.1.tgz#521a22d3d4c545fe1d5eea19eac0fd3ac5e58b1b" + integrity sha512-zzi+fxU/SBad4irdLLsG3yhZgXWZezraGYVQfZFWe8kl7W/EHUk+Eqk/eetn4bDeJ6ltQskX+UXH3OP5Vh0Q0Q== dependencies: d3-delaunay "^6.0.2" - vega-dataflow "^5.7.3" - vega-util "^1.15.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-wordcloud@~4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.3.tgz#ce90900333f4e0d3ee706ba4f36bb0905f8b4a9f" - integrity sha512-is4zYn9FMAyp9T4SAcz2P/U/wqc0Lx3P5YtpWKCbOH02a05vHjUQrQ2TTPOuvmMfAEDCSKvbMSQIJMOE018lJA== - dependencies: - vega-canvas "^1.2.5" - vega-dataflow "^5.7.3" - vega-scale "^7.1.1" - vega-statistics "^1.7.9" - vega-util "^1.15.2" - -vega@^5.22.1: - version "5.22.1" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.22.1.tgz#e028f3645de18e0070317bc04410282975549e1e" - integrity sha512-KJBI7OWSzpfCPbmWl3GQCqBqbf2TIdpWS0mzO6MmWbvdMhWHf74P9IVnx1B1mhg0ZTqWFualx9ZYhWzMMwudaQ== - dependencies: - vega-crossfilter "~4.1.0" - vega-dataflow "~5.7.4" - vega-encode "~4.9.0" - vega-event-selector "~3.0.0" - vega-expression "~5.0.0" - vega-force "~4.1.0" - vega-format "~1.1.0" - vega-functions "~5.13.0" - vega-geo "~4.4.0" - vega-hierarchy "~4.1.0" - vega-label "~1.2.0" - vega-loader "~4.5.0" - vega-parser "~6.1.4" - vega-projection "~1.5.0" - vega-regression "~1.1.0" - vega-runtime "~6.1.3" - vega-scale "~7.2.0" - vega-scenegraph "~4.10.1" - vega-statistics "~1.8.0" - vega-time "~2.1.0" - vega-transforms "~4.10.0" - vega-typings "~0.22.0" - vega-util "~1.17.0" - vega-view "~5.11.0" - vega-view-transforms "~4.5.8" - vega-voronoi "~4.2.0" - vega-wordcloud "~4.1.3" +vega-wordcloud@~4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.4.tgz#38584cf47ef52325d6a8dc38908b5d2378cc6e62" + integrity sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw== + dependencies: + vega-canvas "^1.2.7" + vega-dataflow "^5.7.5" + vega-scale "^7.3.0" + vega-statistics "^1.8.1" + vega-util "^1.17.1" + +vega@^5.23.0: + version "5.23.0" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.23.0.tgz#7e3899b65f1a84095545b74dcf71289890844c49" + integrity sha512-FjgDD/VmL9yl36ByLq66mEusDF/wZGRktK4JA5MkF68hQqj3F8BFMDDVNwCASuwY97H6wr7kw/RFqNI6XocjJQ== + dependencies: + vega-crossfilter "~4.1.1" + vega-dataflow "~5.7.5" + vega-encode "~4.9.1" + vega-event-selector "~3.0.1" + vega-expression "~5.0.1" + vega-force "~4.1.1" + vega-format "~1.1.1" + vega-functions "~5.13.1" + vega-geo "~4.4.1" + vega-hierarchy "~4.1.1" + vega-label "~1.2.1" + vega-loader "~4.5.1" + vega-parser "~6.2.0" + vega-projection "~1.6.0" + vega-regression "~1.1.1" + vega-runtime "~6.1.4" + vega-scale "~7.3.0" + vega-scenegraph "~4.10.2" + vega-statistics "~1.8.1" + vega-time "~2.1.1" + vega-transforms "~4.10.1" + vega-typings "~0.23.0" + vega-util "~1.17.1" + vega-view "~5.11.1" + vega-view-transforms "~4.5.9" + vega-voronoi "~4.2.1" + vega-wordcloud "~4.1.4" verror@1.10.0: version "1.10.0"