diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 4adb920c81e43..1400d1fdee4cf 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -359,4 +359,5 @@ enabled: - x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts - x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts - x-pack/performance/journeys/dashboard_listing_page.ts + - x-pack/performance/journeys/cloud_security_dashboard.ts - x-pack/test/custom_branding/config.ts diff --git a/.buildkite/pipelines/code_coverage/daily.yml b/.buildkite/pipelines/code_coverage/daily.yml index a2b73329b4b01..ab6fdfc29ad87 100644 --- a/.buildkite/pipelines/code_coverage/daily.yml +++ b/.buildkite/pipelines/code_coverage/daily.yml @@ -20,7 +20,7 @@ steps: - command: .buildkite/scripts/steps/code_coverage/ingest.sh label: 'Merge and Ingest' agents: - queue: c2-16 + queue: n2-4-spot depends_on: - jest - jest-integration diff --git a/.buildkite/scripts/steps/functional/apm_cypress.sh b/.buildkite/scripts/steps/functional/apm_cypress.sh index ff14df87377cd..987d9de577c8b 100755 --- a/.buildkite/scripts/steps/functional/apm_cypress.sh +++ b/.buildkite/scripts/steps/functional/apm_cypress.sh @@ -12,8 +12,8 @@ APM_CYPRESS_RECORD_KEY="$(retry 5 5 vault read -field=CYPRESS_RECORD_KEY secret/ export JOB=kibana-apm-cypress IS_FLAKY_TEST_RUNNER=${CLI_COUNT:-0} -# Disable parallel tests and dashboard recording when running them in the flaky test runner -if [[ "$IS_FLAKY_TEST_RUNNER" -ne 1 ]]; then +#Enabling cypress dashboard recording when PR is labeled with `apm:cypress-record` and we are not using the flaky test runner +if [[ "$IS_FLAKY_TEST_RUNNER" -ne 1 ]] && is_pr_with_label "apm:cypress-record"; then CYPRESS_ARGS="--record --key "$APM_CYPRESS_RECORD_KEY" --parallel --ci-build-id "${BUILDKITE_BUILD_ID}"" else CYPRESS_ARGS="" diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index fbe8daee88370..949cb0a0ff534 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -19,7 +19,7 @@ const STORYBOOKS = [ 'cloud_chat', 'coloring', 'chart_icons', - 'content_management_plugin', + 'content_management_examples', 'controls', 'custom_integrations', 'dashboard_enhanced', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4a9a53cdce54f..5d4277e9436eb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ x-pack/examples/alerting_example @elastic/response-ops x-pack/test/functional_with_es_ssl/plugins/alerts @elastic/response-ops x-pack/plugins/alerting @elastic/response-ops packages/kbn-alerts @elastic/security-solution +packages/kbn-alerts-as-data-utils @elastic/response-ops x-pack/test/alerting_api_integration/common/plugins/alerts_restricted @elastic/response-ops packages/kbn-alerts-ui-shared @elastic/response-ops packages/kbn-ambient-common-types @elastic/kibana-operations @@ -82,6 +83,7 @@ packages/kbn-config-mocks @elastic/kibana-core packages/kbn-config-schema @elastic/kibana-core src/plugins/console @elastic/platform-deployment-management packages/content-management/content_editor @elastic/appex-sharedux +examples/content_management_examples @elastic/appex-sharedux src/plugins/content_management @elastic/appex-sharedux packages/content-management/table_list @elastic/appex-sharedux examples/controls_example @elastic/kibana-presentation @@ -337,6 +339,7 @@ x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin src/plugins/event_annotation @elastic/kibana-visualizations x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops x-pack/plugins/event_log @elastic/response-ops +packages/kbn-expandable-flyout @elastic/security-threat-hunting-investigations packages/kbn-expect @elastic/kibana-operations x-pack/examples/exploratory_view_example @elastic/uptime src/plugins/expression_error @elastic/kibana-presentation @@ -1156,12 +1159,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 diff --git a/.github/workflows/skip-failed-test.yml b/.github/workflows/skip-failed-test.yml index e892582951adc..a6535b106c728 100644 --- a/.github/workflows/skip-failed-test.yml +++ b/.github/workflows/skip-failed-test.yml @@ -26,6 +26,7 @@ jobs: uses: ./actions/permission-check with: permission: admin + teams: appex-qa token: ${{secrets.GITHUB_TOKEN}} - name: Checkout kibana-operations diff --git a/.i18nrc.json b/.i18nrc.json index d8a6b4689f78e..b7d7432551faa 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -25,6 +25,7 @@ "embeddableExamples": "examples/embeddable_examples", "esQuery": "packages/kbn-es-query/src", "esUi": "src/plugins/es_ui_shared", + "expandableFlyout": "packages/kbn-expandable-flyout", "expressionError": "src/plugins/expression_error", "expressionGauge": "src/plugins/chart_expressions/expression_gauge", "expressionHeatmap": "src/plugins/chart_expressions/expression_heatmap", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 786d5ef0ebff9..eb18c12c4c087 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-27 +date: 2023-03-01 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 6b5482bad9600..457c9a044a4f5 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-27 +date: 2023-03-01 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 6bf96b8b294b8..a27f9db685c13 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 0a9c5b5718503..016e965013dfe 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -867,6 +867,69 @@ } ], "functions": [ + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate", + "type": "Function", + "tags": [], + "label": "getComponentTemplate", + "description": [], + "signature": [ + "(fieldMap: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + }, + ", context?: string | undefined) => ", + "ClusterPutComponentTemplateRequest" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate.$1", + "type": "Object", + "tags": [], + "label": "fieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate.$2", + "type": "string", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-server.getEsErrorMessage", @@ -3239,6 +3302,33 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-server.ECS_COMPONENT_TEMPLATE_NAME", + "type": "string", + "tags": [], + "label": "ECS_COMPONENT_TEMPLATE_NAME", + "description": [], + "path": "x-pack/plugins/alerting/server/alerts_service/alerts_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "alerting", + "id": "def-server.ECS_CONTEXT", + "type": "string", + "tags": [], + "label": "ECS_CONTEXT", + "description": [], + "signature": [ + "\"ecs\"" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/alerts_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-server.ExecutorType", @@ -3938,6 +4028,42 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.getComponentTemplateFromFieldMap", + "type": "Function", + "tags": [], + "label": "getComponentTemplateFromFieldMap", + "description": [], + "signature": [ + "({ name, fieldMap, fieldLimit, }: ", + "GetComponentTemplateFromFieldMapOpts", + ") => ", + "ClusterPutComponentTemplateRequest" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-common.getComponentTemplateFromFieldMap.$1", + "type": "Object", + "tags": [], + "label": "{\n name,\n fieldMap,\n fieldLimit,\n}", + "description": [], + "signature": [ + "GetComponentTemplateFromFieldMapOpts" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.getDurationNumberInItsUnit", @@ -4052,6 +4178,69 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap", + "type": "Function", + "tags": [], + "label": "mappingFromFieldMap", + "description": [], + "signature": [ + "(fieldMap: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + }, + ", dynamic: boolean | \"strict\") => ", + "MappingTypeMapping" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap.$1", + "type": "Object", + "tags": [], + "label": "fieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap.$2", + "type": "CompoundType", + "tags": [], + "label": "dynamic", + "description": [], + "signature": [ + "boolean | \"strict\"" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.parseDuration", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index be7ba2627c4d1..f05c201efa2b2 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 487 | 1 | 476 | 40 | +| 497 | 1 | 486 | 41 | ## Client diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 1be070da0fd6c..da0ef9b627636 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-27 +date: 2023-03-01 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 404ec374f5d93..7fedab8ec9cf3 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-27 +date: 2023-03-01 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 5c2d6f7fd9abf..ec54a22e05b23 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-27 +date: 2023-03-01 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 7caa2bc00fb8b..22ee299448164 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 3ab0fa50805ff..ca31ede36cb35 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 122899a005d34..6dc629e35d2dc 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-27 +date: 2023-03-01 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 b4e6fecfc3cca..87cefa21ff0d3 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-27 +date: 2023-03-01 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 a470cd5439021..5dd10df91ff20 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-27 +date: 2023-03-01 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 a901c6b14ae16..84f7145933da0 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-27 +date: 2023-03-01 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 a75eaeb7a2b0b..ffff93a8d3d3c 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-27 +date: 2023-03-01 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 ea6376cd43bee..58f563e56b4c0 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-27 +date: 2023-03-01 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 f065f7c6053c8..9f76e7a05a1bd 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-27 +date: 2023-03-01 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 e008179a519e2..35ea8b7e28ddc 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-27 +date: 2023-03-01 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 f14ff89397cde..cf0ca55516578 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-27 +date: 2023-03-01 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 99a0b87121e18..6f4c31dc6eb24 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-27 +date: 2023-03-01 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 f011348359e18..217d118b83fd9 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-27 +date: 2023-03-01 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 b341979bce4f1..e4fbf40660418 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-27 +date: 2023-03-01 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 f84933f0b1b4e..236ea0cd5f1ab 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-27 +date: 2023-03-01 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..7759142291791 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" } ] }, diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 717733b31880f..a8ff6c781ceec 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-27 +date: 2023-03-01 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 2eb4ba583ca7d..18f87793f4dad 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-27 +date: 2023-03-01 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 55145af95934b..20d3b7417d133 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-27 +date: 2023-03-01 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 2c803c9c2d839..8401c7e3aa4c0 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-27 +date: 2023-03-01 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 79135b32e8e81..d8f6a38d7d905 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-27 +date: 2023-03-01 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 b685ec997b5d0..6e13f172d3f04 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 0ca6b88b3bb74..2aaecd805c8ae 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 12a25580eb6e2..e4096996b690e 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-27 +date: 2023-03-01 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 ed6456585150b..b17477ae78a6f 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -23,11 +23,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | 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 90479084fc698..80094a42fb4ef 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-27 +date: 2023-03-01 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) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a4257edc748df..83b680c45c6d5 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index ccf63dda93828..a5be54c3a993b 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-27 +date: 2023-03-01 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 ddef23b4a631e..68d7e71d19fcd 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-27 +date: 2023-03-01 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 28887555fed58..207042f8c205a 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-27 +date: 2023-03-01 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 36b3d4dcf7acb..997bebea4a967 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-27 +date: 2023-03-01 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..9c774d762c515 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", @@ -10711,6 +10764,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 a86f88c5770a3..7dbe14444d9f0 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-27 +date: 2023-03-01 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 | +| 538 | 9 | 435 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index c6159057905f2..83ae1d0c3da02 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-27 +date: 2023-03-01 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 915fbca25d243..bc81ba849ed3d 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-27 +date: 2023-03-01 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 ac199094d61f5..bb071de249983 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-27 +date: 2023-03-01 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 93f25aafbaab0..98c959696e829 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-27 +date: 2023-03-01 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 9dc9bbfad5134..9f01ece99d2d5 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index 960b7a226d30c..ef90f20d24aa0 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1514,7 +1514,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" + "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1534,7 +1534,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1549,7 +1549,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}> | undefined" + "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 77e029e48299c..80a488bc472c6 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-27 +date: 2023-03-01 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 d2cde25c51a23..8c666366fc8d6 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-27 +date: 2023-03-01 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 11d527ee9f30b..9fdec0dc8cb79 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-27 +date: 2023-03-01 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 04dbccdf0997f..ec04a0683a4f1 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-27 +date: 2023-03-01 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 286140f0d24b6..d7b940077fb77 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-27 +date: 2023-03-01 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 95c8fae0e37c9..a619d11728ba2 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-27 +date: 2023-03-01 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 692a945b7e667..30bec93fff3c6 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-27 +date: 2023-03-01 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 a15b827070bde..eeec105f003cc 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-27 +date: 2023-03-01 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 192785384e81e..0908cbf569b2b 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-27 +date: 2023-03-01 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 73e7aa1f1b7e5..a826b9492c3ea 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-27 +date: 2023-03-01 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 caf5c83c2ab06..56f581eff2739 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-27 +date: 2023-03-01 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 7ea256e85e08d..c3ec86cf5b9a8 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-27 +date: 2023-03-01 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 247988910c3e6..215621e2f70de 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-27 +date: 2023-03-01 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 b3424677d1306..9ca0c092ed412 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-27 +date: 2023-03-01 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 fc26a51f9c653..3cd9a6d0a8187 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-27 +date: 2023-03-01 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 5ef6733937074..60074a15c73e2 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-27 +date: 2023-03-01 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 8bea1b1e8715d..728d3f0f26f7a 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-27 +date: 2023-03-01 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 e8f573925ec16..c4e44c06c233e 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-27 +date: 2023-03-01 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 462d19625454f..3c2201044be35 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -16,7 +16,13 @@ "signature": [ "FilesClient", " extends ", - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, "" ], "path": "src/plugins/files/common/files_client.ts", @@ -203,17 +209,77 @@ "text": "FilesMetrics" }, "; publicDownload: any; find: { files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }; bulkDelete: { succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }; create: { file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }; delete: { ok: true; }; getById: { file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }; list: { files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }; update: { file: ", - "FileJSON", - "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: any; unshare: { ok: true; }; getShare: { share: FileShareJSON; }; listShares: { shares: FileShareJSON[]; }; getFileKind: ", - "FileKind", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + " & { token: string; }; unshare: { ok: true; }; getShare: { share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "; }; listShares: { shares: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }; getFileKind: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + }, "; }" ], "path": "src/plugins/files/common/files_client.ts", @@ -240,332 +306,222 @@ "text": "FilesMetrics" }, ">; publicDownload: (arg: Omit<{ token: string; fileName?: string | undefined; }, \"kind\">) => any; find: (arg: Omit<{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", - "Pagination", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, " & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>; bulkDelete: (arg: Omit<{ ids: string[]; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }>; create: (arg: Omit<{ name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; delete: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; list: (arg?: Omit<{ kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", - "Pagination", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, " & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\"> | undefined) => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>; update: (arg: Omit<{ id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; upload: (arg: Omit<{ id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ fileName?: string | undefined; id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise; getDownloadHref: (arg: Omit, \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit<{ name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", - "Abortable", - ", \"kind\">) => Promise; unshare: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ", \"kind\">) => Promise<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSONWithToken", + "text": "FileShareJSONWithToken" + }, + ">; unshare: (arg: Omit<{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", - ", \"kind\">) => Promise<{ share: FileShareJSON; }>; listShares: (arg: Omit<{ forFileId?: string | undefined; kind: string; } & ", - "Pagination", - " & ", - "Abortable", - ", \"kind\">) => Promise<{ shares: FileShareJSON[]; }>; getFileKind: (arg: Omit) => ", - "FileKind", - "; }" - ], - "path": "src/plugins/files/common/files_client.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind", - "type": "Object", - "tags": [], - "label": "defaultImageFileKind", - "description": [ - "\nA file kind that is available to all plugins to use for uploading images\nintended to be reused across Kibana." - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "\"defaultImage\"" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" }, + ", \"kind\">) => Promise<{ share: ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.maxSizeBytes", - "type": "number", - "tags": [], - "label": "maxSizeBytes", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" }, + "; }>; listShares: (arg: Omit<{ forFileId?: string | undefined; kind: string; } & ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.blobStoreSettings", - "type": "Object", - "tags": [], - "label": "blobStoreSettings", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [] + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" }, + " & ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.allowedMimeTypes", - "type": "Array", - "tags": [], - "label": "allowedMimeTypes", - "description": [ - "// tried using \"image/*\" but it did not work with the HTTP endpoint (got 415 Unsupported Media Type)" - ], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" }, + ", \"kind\">) => Promise<{ shares: ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.create.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.delete.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.download", - "type": "Object", - "tags": [], - "label": "download", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.download.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.getById", - "type": "Object", - "tags": [], - "label": "getById", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.getById.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.list", - "type": "Object", - "tags": [], - "label": "list", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.list.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.share", - "type": "Object", - "tags": [], - "label": "share", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.share.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.update.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ] - } + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }>; getFileKind: (arg: Omit) => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + }, + "; }" ], + "path": "src/plugins/files/common/files_client.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false } ], + "objects": [], "setup": { "parentPluginId": "files", "id": "def-public.FilesSetup", @@ -588,7 +544,7 @@ ], "label": "filesClientFactory", "description": [ - "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKind}.\n" + "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKindBrowser}.\n" ], "signature": [ "FilesClientFactory" @@ -618,7 +574,13 @@ ], "signature": [ "(fileKind: ", - "FileKind", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, ") => void" ], "path": "src/plugins/files/public/plugin.ts", @@ -635,7 +597,13 @@ "- the file kind to register" ], "signature": [ - "FileKind" + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + } ], "path": "src/plugins/files/public/plugin.ts", "deprecated": false, @@ -779,7 +747,7 @@ "tags": [], "label": "elasticsearchClient", "description": [ - "\nAn elasticsearch client that will be used to interact with the cluster" + "\nAn elasticsearch client that will be used to interact with the cluster." ], "signature": [ "{ create: { (this: That, params: ", @@ -1981,7 +1949,7 @@ "tags": [], "label": "maxSizeBytes", "description": [ - "\nThe maximum file size to be write" + "\nThe maximum file size to be written." ], "signature": [ "number | undefined" @@ -1997,7 +1965,7 @@ "tags": [], "label": "logger", "description": [ - "\nA logger for debuggin purposes" + "\nA logger for debugging purposes." ], "signature": [ { @@ -2615,9 +2583,9 @@ "ShareArgs", ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, @@ -2706,9 +2674,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -2793,9 +2761,21 @@ ], "signature": [ "Required> & ", - "BaseFileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, " & { FileKind: string; Meta?: M | undefined; }" ], "path": "src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts", @@ -3514,7 +3494,13 @@ "text": "FindFileArgs" }, ") => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>" ], "path": "src/plugins/files/server/file_service/file_service.ts", @@ -3559,9 +3545,9 @@ "signature": [ "(arg: IdArg) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3610,9 +3596,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3667,9 +3653,9 @@ }, ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShare", "text": "FileShare" }, @@ -3837,9 +3823,9 @@ "signature": [ "(arg: IdArg) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3889,9 +3875,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3947,9 +3933,9 @@ }, ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShare", "text": "FileShare" }, @@ -4404,7 +4390,13 @@ ], "signature": [ "{ name?: string | undefined; created?: string | undefined; Status?: \"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\" | undefined; Updated?: string | undefined; mime_type?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; ChunkSize?: number | undefined; Compression?: ", - "FileCompression", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, " | undefined; FileKind?: string | undefined; Meta?: M | undefined; }" ], "path": "src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts", @@ -4569,7 +4561,13 @@ ], "signature": [ "(fileKind: ", - "FileKind", + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, ") => void" ], "path": "src/plugins/files/server/types.ts", @@ -4587,7 +4585,13 @@ "- the file kind to register" ], "signature": [ - "FileKind" + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + } ], "path": "src/plugins/files/server/types.ts", "deprecated": false, @@ -4726,7 +4730,13 @@ "\nFile metadata in camelCase form." ], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "src/plugins/files/common/types.ts", @@ -4921,9 +4931,9 @@ }, " | undefined) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, @@ -4972,9 +4982,9 @@ "signature": [ "() => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -5047,7 +5057,13 @@ ], "signature": [ "() => ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "src/plugins/files/common/types.ts", @@ -5069,10 +5085,16 @@ "\nAttributes of a file that represent a serialised version of the file." ], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -5085,7 +5107,7 @@ "description": [ "\nUnique file ID." ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5098,7 +5120,7 @@ "description": [ "\nISO string of when this file was created" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5111,7 +5133,7 @@ "description": [ "\nISO string of when the file was updated" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5126,7 +5148,7 @@ "description": [ "\nFile name.\n" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5142,7 +5164,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5158,7 +5180,7 @@ "signature": [ "number | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5176,7 +5198,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5192,7 +5214,7 @@ "signature": [ "Meta | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5208,7 +5230,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5223,7 +5245,7 @@ "description": [ "\nA unique kind that governs various aspects of the file. A consumer of the\nfiles service must register a file kind and link their files to a specific\nkind.\n" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5239,7 +5261,7 @@ "signature": [ "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5255,7 +5277,7 @@ "signature": [ "{ name?: string | undefined; id?: string | undefined; } | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false } @@ -5269,72 +5291,73 @@ "tags": [], "label": "FileKind", "description": [], - "path": "packages/shared-ux/file/types/index.d.ts", + "signature": [ + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false, "children": [ - { - "parentPluginId": "files", - "id": "def-common.FileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [ - "\nUnique file kind ID" - ], - "path": "packages/shared-ux/file/types/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "files", "id": "def-common.FileKind.maxSizeBytes", - "type": "number", + "type": "CompoundType", "tags": [ "default" ], "label": "maxSizeBytes", "description": [ - "\nMaximum size, in bytes, a file of this kind can be.\n" + "\nMax file contents size, in bytes. Can be customized per file using the\n{@link FileJSON} object. This is enforced on the server-side as well as\nin the upload React component.\n" ], "signature": [ - "number | undefined" - ], - "path": "packages/shared-ux/file/types/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.FileKind.allowedMimeTypes", - "type": "Array", - "tags": [ - "default" - ], - "label": "allowedMimeTypes", - "description": [ - "\nThe MIME type of the file content.\n" - ], - "signature": [ - "string[] | undefined" + "number | ((file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ") => number) | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "files", "id": "def-common.FileKind.blobStoreSettings", - "type": "Any", + "type": "Object", "tags": [], "label": "blobStoreSettings", "description": [ "\nBlob store specific settings that enable configuration of storage\ndetails." ], "signature": [ - "any" + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.BlobStorageSettings", + "text": "BlobStorageSettings" + }, + " | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false }, @@ -5352,7 +5375,56 @@ "signature": [ "{ create?: HttpEndpointDefinition | undefined; update?: HttpEndpointDefinition | undefined; delete?: HttpEndpointDefinition | undefined; getById?: HttpEndpointDefinition | undefined; list?: HttpEndpointDefinition | undefined; download?: HttpEndpointDefinition | undefined; share?: HttpEndpointDefinition | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-common.FileKindBrowser", + "type": "Interface", + "tags": [], + "label": "FileKindBrowser", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-common.FileKindBrowser.maxSizeBytes", + "type": "number", + "tags": [ + "default" + ], + "label": "maxSizeBytes", + "description": [ + "\nMax file contents size, in bytes, enforced for this file kind in the upload\ncomponent.\n" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false } @@ -5368,7 +5440,7 @@ "description": [ "\nAttributes of a file that represent a serialised version of the file." ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -5381,7 +5453,7 @@ "description": [ "\nUnique ID share instance" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5394,7 +5466,7 @@ "description": [ "\nISO timestamp the share was created" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5407,7 +5479,7 @@ "description": [ "\nUnix timestamp (in milliseconds) of when this share expires" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5423,7 +5495,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5436,7 +5508,7 @@ "description": [ "\nThe ID of the file this share is linked to" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false } @@ -5648,12 +5720,24 @@ ], "signature": [ "{ name?: string | undefined; mime_type?: string | undefined; created?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; Updated?: string | undefined; Status?: ", - "FileStatus", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileStatus", + "text": "FileStatus" + }, " | undefined; ChunkSize?: number | undefined; Compression?: ", - "FileCompression", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, " | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5704,7 +5788,7 @@ "signature": [ "\"none\" | \"br\" | \"gzip\" | \"deflate\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5720,12 +5804,24 @@ ], "signature": [ "Required> & ", - "BaseFileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, " & { FileKind: string; Meta?: Meta | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5748,7 +5844,13 @@ "text": "SavedObject" }, "<", - "FileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileMetadata", + "text": "FileMetadata" + }, ">" ], "path": "src/plugins/files/common/types.ts", @@ -5768,7 +5870,7 @@ "signature": [ "{ created: string; token: string; name?: string | undefined; valid_until: number; }" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5786,15 +5888,15 @@ ], "signature": [ { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, " & { token: string; }" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5809,7 +5911,7 @@ "signature": [ "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5883,278 +5985,6 @@ "initialIsOpen": false } ], - "objects": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind", - "type": "Object", - "tags": [], - "label": "defaultImageFileKind", - "description": [ - "\nA file kind that is available to all plugins to use for uploading images\nintended to be reused across Kibana." - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "\"defaultImage\"" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.maxSizeBytes", - "type": "number", - "tags": [], - "label": "maxSizeBytes", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.blobStoreSettings", - "type": "Object", - "tags": [], - "label": "blobStoreSettings", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.allowedMimeTypes", - "type": "Array", - "tags": [], - "label": "allowedMimeTypes", - "description": [ - "// tried using \"image/*\" but it did not work with the HTTP endpoint (got 415 Unsupported Media Type)" - ], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.create.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.delete.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.download", - "type": "Object", - "tags": [], - "label": "download", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.download.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.getById", - "type": "Object", - "tags": [], - "label": "getById", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.getById.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.list", - "type": "Object", - "tags": [], - "label": "list", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.list.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.share", - "type": "Object", - "tags": [], - "label": "share", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.share.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.update.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ] - } - ], - "initialIsOpen": false - } - ] + "objects": [] } } \ No newline at end of file diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 5b94fadff8259..aec2d7668382f 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.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 | |-------------------|-----------|------------------------|-----------------| -| 254 | 1 | 45 | 5 | +| 214 | 0 | 10 | 5 | ## Client @@ -31,9 +31,6 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start -### Objects - - ### Interfaces @@ -59,9 +56,6 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ## Common -### Objects - - ### Interfaces diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index b916774dbdb67..0c7c522d8cd52 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-27 +date: 2023-03-01 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 f9a49b8123b49..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", @@ -25180,6 +25261,17 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.EPM_API_ROUTES.VERIFICATION_KEY_ID", + "type": "string", + "tags": [], + "label": "VERIFICATION_KEY_ID", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.EPM_API_ROUTES.STATS_PATTERN", @@ -25238,6 +25330,22 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.epmRouteService.getVerificationKeyIdPath", + "type": "Function", + "tags": [], + "label": "getVerificationKeyIdPath", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-common.epmRouteService.getCategoriesPath", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 75c2497452fab..12c56be0b77ba 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-27 +date: 2023-03-01 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 | |-------------------|-----------|------------------------|-----------------| -| 1087 | 3 | 982 | 27 | +| 1094 | 3 | 989 | 27 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 63bc515caf0ed..5d9f0fa08839e 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-27 +date: 2023-03-01 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 062c0047405d4..c5c5a26a41c68 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-27 +date: 2023-03-01 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 768a848a4aab2..707ae9a95aac6 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-27 +date: 2023-03-01 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 4780f3b4c14be..6e42d9a770f91 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-27 +date: 2023-03-01 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 c97553b7c1299..dfeb32bb9975d 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-27 +date: 2023-03-01 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 3a58461fc3584..d3a18506f2bce 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-27 +date: 2023-03-01 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 18968689e11dd..8a11e37afd294 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-27 +date: 2023-03-01 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 d439b28ace1d2..adf41779fff68 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-27 +date: 2023-03-01 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 626a3739cfdc0..efcd3b891dbc6 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-27 +date: 2023-03-01 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 0fa12f26cf43e..d1da2608b6daa 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-27 +date: 2023-03-01 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 4824f734f9101..7e21ddd11bfd1 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-27 +date: 2023-03-01 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 dc8cac3bee834..12024f602f215 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-27 +date: 2023-03-01 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 d42a9f6a264a9..94b33154d07b0 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.devdocs.json b/api_docs/kbn_alerts_as_data_utils.devdocs.json new file mode 100644 index 0000000000000..b5390c5044148 --- /dev/null +++ b/api_docs/kbn_alerts_as_data_utils.devdocs.json @@ -0,0 +1,215 @@ +{ + "id": "@kbn/alerts-as-data-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.FieldMap", + "type": "Interface", + "tags": [], + "label": "FieldMap", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.FieldMap.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: { type: string; required: boolean; array?: boolean | undefined; doc_values?: boolean | undefined; enabled?: boolean | undefined; format?: string | undefined; ignore_above?: number | undefined; ... 4 more ...; dynamic?: boolean | ... 1 more ... | undefined; }", + "description": [], + "signature": [ + "[key: string]: { type: string; required: boolean; array?: boolean | undefined; doc_values?: boolean | undefined; enabled?: boolean | undefined; format?: string | undefined; ignore_above?: number | undefined; multi_fields?: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.MultiField", + "text": "MultiField" + }, + "[] | undefined; index?: boolean | undefined; path?: string | undefined; scaling_factor?: number | undefined; dynamic?: boolean | \"strict\" | undefined; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField", + "type": "Interface", + "tags": [], + "label": "MultiField", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.flat_name", + "type": "string", + "tags": [], + "label": "flat_name", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.AlertFieldMap", + "type": "Type", + "tags": [], + "label": "AlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.EcsFieldMap", + "type": "Type", + "tags": [], + "label": "EcsFieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.LegacyAlertFieldMap", + "type": "Type", + "tags": [], + "label": "LegacyAlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.risk_score\": { readonly type: \"float\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.author\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.description\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.enabled\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.from\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.interval\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.license\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.note\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.references\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.rule_name_override\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.to\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.severity\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.docs_count\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.field\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.suppression.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.value\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.system_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_user\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"ecs.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.alertFieldMap", + "type": "Object", + "tags": [], + "label": "alertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.ecsFieldMap", + "type": "Object", + "tags": [], + "label": "ecsFieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.legacyAlertFieldMap", + "type": "Object", + "tags": [], + "label": "legacyAlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.risk_score\": { readonly type: \"float\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.author\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.description\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.enabled\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.from\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.interval\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.license\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.note\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.references\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.rule_name_override\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.to\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.severity\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.docs_count\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.field\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.suppression.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.value\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.system_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_user\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"ecs.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx new file mode 100644 index 0000000000000..8c6a728b96826 --- /dev/null +++ b/api_docs/kbn_alerts_as_data_utils.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: kibKbnAlertsAsDataUtilsPluginApi +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-03-01 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] +--- +import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; + + + +Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 12 | 0 | + +## Common + +### Objects + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 4feef26b61869..e19c88b434998 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-27 +date: 2023-03-01 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 b43b2e542aa7a..a561d8cd44c93 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-27 +date: 2023-03-01 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 83c0c9326f5e9..6c9c67c02e850 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-27 +date: 2023-03-01 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 8444fb1c70527..50a6c08fa198b 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-27 +date: 2023-03-01 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 5598eab2e267c..e9998ea2c1028 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-27 +date: 2023-03-01 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 89b4d27e88db7..8eaf4e8926976 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-27 +date: 2023-03-01 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 7ed85b3e2a725..cef0978935b54 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-27 +date: 2023-03-01 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 3d976b803011c..6b9fb907ac953 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-27 +date: 2023-03-01 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 293ec377ece5b..ed07b9e5fa4c8 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-27 +date: 2023-03-01 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 220c4d0eff04c..0649db4100861 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-27 +date: 2023-03-01 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 ea59a115a467f..ff0b1e2cf64e5 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -2299,7 +2299,7 @@ "section": "def-common.ApmFields", "text": "ApmFields" }, - ", \"@timestamp\" | \"metricset.name\" | \"ecs.version\" | \"event.ingested\" | \"observer.type\" | \"observer.version\" | \"observer.version_major\" | \"processor.event\" | \"processor.name\"> & Partial<{ 'labels.etag': string; agent_config_applied: number; 'event.agent_id_status': string; }>" + ", \"@timestamp\" | \"metricset.name\" | \"ecs.version\" | \"event.ingested\" | \"observer.type\" | \"observer.version_major\" | \"observer.version\" | \"processor.event\" | \"processor.name\"> & Partial<{ 'labels.etag': string; agent_config_applied: number; 'event.agent_id_status': string; }>" ], "path": "packages/kbn-apm-synthtrace-client/src/lib/agent_config/agent_config_fields.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 375c04442cbc2..58f1ad8f586fc 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index b8ef3faac8e19..ceb84d0bca94c 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-27 +date: 2023-03-01 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 ddf1730e45ac9..1aecaa5c2de78 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-27 +date: 2023-03-01 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 3511b505e0e2f..08bdd99ee670e 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-27 +date: 2023-03-01 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 02061af2d14dd..83573b77a2ea3 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-27 +date: 2023-03-01 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 deb309320b4bb..5d002c32c79c1 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-27 +date: 2023-03-01 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 bf64afd743b3b..08e14a926934e 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-27 +date: 2023-03-01 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 746317a3feac4..cf1d231ea3a8a 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-27 +date: 2023-03-01 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 b3a7d25e34933..4927891257687 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-27 +date: 2023-03-01 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 c2ae9b8a179bc..d5477842f3195 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-27 +date: 2023-03-01 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 c7dfe9733b1e1..f108522506807 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-27 +date: 2023-03-01 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 92cc1a4550224..79106555bf1b3 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-27 +date: 2023-03-01 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 86b1ef2ecb1a1..c60be8d19bbc4 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-27 +date: 2023-03-01 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 849cc7d107d55..1d3df732a5c52 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-27 +date: 2023-03-01 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 cb251ab0c54fe..e9c939110219b 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-27 +date: 2023-03-01 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 3239215bb6adf..7cb770f30088b 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-27 +date: 2023-03-01 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 711b3e8bd807a..502f68f9bafc6 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-27 +date: 2023-03-01 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 09e4ce4f3836f..24dd88818d012 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-27 +date: 2023-03-01 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 9ab4d41b30e87..19be442f12401 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-27 +date: 2023-03-01 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 3a3df328dc8d7..3cf223d811a50 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-27 +date: 2023-03-01 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 81db7f3f0ab31..15c50b14ddbcc 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-27 +date: 2023-03-01 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 15cfcda31f131..949d647c0fb4e 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-27 +date: 2023-03-01 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 eb94716c82be0..423d3d61683c8 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-27 +date: 2023-03-01 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 1535727bc8d26..611a4afcf75de 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-27 +date: 2023-03-01 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 7347a63bc8e23..304c873831fd4 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-27 +date: 2023-03-01 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 072e69f42cab8..25efcb0bb8a3a 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-27 +date: 2023-03-01 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 bd3b0383bc332..892c8c76b89b1 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-27 +date: 2023-03-01 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 993b94c6655ce..fd8b79f885895 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-27 +date: 2023-03-01 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 068e2faf47ed6..d4b49e2544472 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-27 +date: 2023-03-01 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 721be4c216c89..52e6b016b9574 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-27 +date: 2023-03-01 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 0a109d63443f6..ef0f271766eb3 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-27 +date: 2023-03-01 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 c2bc58640090a..6f270b901e057 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-27 +date: 2023-03-01 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 b975c09f45aaf..56dd0c4a1e434 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-27 +date: 2023-03-01 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 c7e8c5b615357..b2aeca83bc43c 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-27 +date: 2023-03-01 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 f1571949332a2..365cb851bbd98 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-27 +date: 2023-03-01 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 e9f1270cf71fb..9cc170403d766 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-27 +date: 2023-03-01 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 0ebeda8790a7c..588562c1397a9 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-27 +date: 2023-03-01 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 4c206c74f1a8d..d298d07536f0e 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-27 +date: 2023-03-01 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 bcc107a33368f..06c64391d23da 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-27 +date: 2023-03-01 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 01a7e68610e7c..5093d5f89d334 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-27 +date: 2023-03-01 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 1de0023acef2e..284888ef0efa2 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-27 +date: 2023-03-01 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 3ed282bbfd36e..897aca49852db 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-27 +date: 2023-03-01 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 c101b8c201c2c..c728f1c726c88 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-27 +date: 2023-03-01 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 580ff5a3b57fe..3bf75aeb93ca0 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-27 +date: 2023-03-01 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 cda93ff8286cd..f39bbcfc2e829 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-27 +date: 2023-03-01 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 20ec492830d9b..8e0adfa179bd7 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-27 +date: 2023-03-01 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 d3df38b317fe0..4fe374fa41898 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-27 +date: 2023-03-01 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 e8f9159b959a9..2a0d3f8cdcfdc 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-27 +date: 2023-03-01 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 8cf17e8a59c8b..0cb7943689f4c 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-27 +date: 2023-03-01 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 8cf281aa41042..afbe971181a21 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-27 +date: 2023-03-01 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 1cf9cc03fd97c..45246eee42da7 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-27 +date: 2023-03-01 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 ad46c1edd9657..b4333d4f00a98 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-27 +date: 2023-03-01 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 33f157f2a212b..945ab8c314633 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-27 +date: 2023-03-01 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 042e4b04bcf52..2ded9a267e91e 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-27 +date: 2023-03-01 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 f8d49d314c606..9611a707c6dc6 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-27 +date: 2023-03-01 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 3857af8276b4a..1aea29fbe68a7 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-27 +date: 2023-03-01 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 ea2ea178cf45d..58be39da715ec 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-27 +date: 2023-03-01 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 7d97ab37e707c..30091366a1a87 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-27 +date: 2023-03-01 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 cf86951be6012..54a70b26db85c 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-27 +date: 2023-03-01 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 baeb86012fd03..50d2412b58338 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-27 +date: 2023-03-01 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 15c05311a73a5..efeac69f19104 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-27 +date: 2023-03-01 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 9281ef198f2ee..d96d09fa696bd 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-27 +date: 2023-03-01 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 c9ea75a6c3b23..057a636ae9fe1 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-27 +date: 2023-03-01 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 a4086757492e5..05f8ec97e7a28 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-27 +date: 2023-03-01 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 a707b2a011962..22b6049591aa8 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-27 +date: 2023-03-01 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 c00e6c8f19b53..aea775f4fbbf8 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-27 +date: 2023-03-01 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 62d29a36829ee..1bd54e9d84665 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-27 +date: 2023-03-01 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 df6cdd92bf87e..d18ea0eb5556b 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-27 +date: 2023-03-01 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 05859ea24e9e8..f4cf588a1fd8b 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-27 +date: 2023-03-01 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 e62babd649cbf..5a758ea2d6657 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-27 +date: 2023-03-01 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 df4f5446a3094..c0c4118d6aff2 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-27 +date: 2023-03-01 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 d32765fbcfc14..c794d34a2c2ed 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-27 +date: 2023-03-01 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 0ec13c09b40dc..d9d062fae9ca7 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-27 +date: 2023-03-01 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 6d07e121f437f..65e0ad45dffbe 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-27 +date: 2023-03-01 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 7be2d7332e660..4acd4a82dab31 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-27 +date: 2023-03-01 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 c9a614378a360..f0f05e7622c07 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-27 +date: 2023-03-01 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 14a37f5004d02..0408099e8cc8c 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-27 +date: 2023-03-01 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 93e0fb83aae93..d0dfa1fd74e5e 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-27 +date: 2023-03-01 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 6fdb476db5757..85dfa7ad743be 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-27 +date: 2023-03-01 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 04d6d8654db89..22496bc87fb99 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-27 +date: 2023-03-01 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 f0fe8987be77a..b4cad3b542bd7 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-27 +date: 2023-03-01 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 bd1b8d72dbcb9..3fc2357f914eb 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-27 +date: 2023-03-01 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 ff966b3d52166..7ac2f6b11f718 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-27 +date: 2023-03-01 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 d2e0cc08f2eb4..f1e3e2fd849bd 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-27 +date: 2023-03-01 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 2bae75d9a5c77..85fd6e03831da 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-27 +date: 2023-03-01 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 6daa81af7f719..aa21951eb20b1 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-27 +date: 2023-03-01 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 5abfd211d3f55..ed909df42d85d 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-27 +date: 2023-03-01 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 9c6b7735df4fd..25e80d604cfcf 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 7132683763250..4baa71f0f02b1 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 974f85a8b55f7..389b11f7db33c 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-27 +date: 2023-03-01 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 0bfca6e59b368..89701f27f330d 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-27 +date: 2023-03-01 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 47db66b5ecc84..a89aa6d00bb67 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-27 +date: 2023-03-01 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 c27bfbd37feac..5ebbce42719eb 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-27 +date: 2023-03-01 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 09dae1b2abf1b..289f2bdc2d395 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-27 +date: 2023-03-01 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 6d56e08c115c5..fafb8cc5d0390 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-27 +date: 2023-03-01 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 9ae37a90003a0..f53885b8e9dac 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-27 +date: 2023-03-01 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 a1f7cb96252a7..1eadd9211d2bc 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-27 +date: 2023-03-01 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 80e25386fc84b..54085404b5a38 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-27 +date: 2023-03-01 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 1011becb38e39..c572a6fa7b685 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-27 +date: 2023-03-01 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..4f364f01dd84f 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" @@ -833,10 +829,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 610ccc9db4916..aedc842ef153f 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-27 +date: 2023-03-01 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 035371808b5f4..75b29d3494e6c 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-27 +date: 2023-03-01 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 2ed4f5fef2db3..1a706abe318b6 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-27 +date: 2023-03-01 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 562f7f28c60f8..bbfd13ed51049 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-27 +date: 2023-03-01 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 1eb48afd95a2b..136c12c56ac4b 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-27 +date: 2023-03-01 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 327d6e7a3ce5a..4ecb58cdd9906 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-27 +date: 2023-03-01 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 83d8ea5c57490..144ae1d9d1e8c 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-27 +date: 2023-03-01 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 c1d2cb23e5972..517f631ad858f 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-27 +date: 2023-03-01 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 a4b7482bfe9e8..e943f5e00a460 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-27 +date: 2023-03-01 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 92b8f41da6d32..fd538cc9343ac 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-27 +date: 2023-03-01 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 af3e01e6ece08..8e2b9d0a15b1e 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-27 +date: 2023-03-01 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 0e40b26c24c4c..853a3c1e872d7 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-27 +date: 2023-03-01 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 6e00b954efbb7..d5aee4c7654e0 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-27 +date: 2023-03-01 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 a238d378d5c79..24332a70fa701 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-27 +date: 2023-03-01 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 9c0ab3290114f..30db9430b487d 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_core_node_server.devdocs.json index 9b0d9eed398dd..92ce832afbc4e 100644 --- a/api_docs/kbn_core_node_server.devdocs.json +++ b/api_docs/kbn_core_node_server.devdocs.json @@ -96,6 +96,19 @@ "path": "packages/core/node/core-node-server/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-node-server", + "id": "def-common.NodeRoles.migrator", + "type": "boolean", + "tags": [], + "label": "migrator", + "description": [ + "\nStart Kibana with the specific purpose of completing the migrations phase then shutting down." + ], + "path": "packages/core/node/core-node-server/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 278c9eddb7fc4..492b442149946 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_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 | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 0 | 0 | +| 6 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_core_node_server_internal.devdocs.json b/api_docs/kbn_core_node_server_internal.devdocs.json index 8db2a68da0239..435988185b457 100644 --- a/api_docs/kbn_core_node_server_internal.devdocs.json +++ b/api_docs/kbn_core_node_server_internal.devdocs.json @@ -87,64 +87,6 @@ ], "enums": [], "misc": [], - "objects": [ - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig", - "type": "Object", - "tags": [], - "label": "nodeConfig", - "description": [], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig.path", - "type": "string", - "tags": [], - "label": "path", - "description": [], - "signature": [ - "\"node\"" - ], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig.schema", - "type": "Object", - "tags": [], - "label": "schema", - "description": [], - "signature": [ - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ObjectType", - "text": "ObjectType" - }, - "<{ roles: ", - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "<\"*\"[] | (\"ui\" | \"background_tasks\")[]>; }>" - ], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ] + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 681584152552f..f7b33406a3820 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; @@ -21,13 +21,10 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 6 | 1 | +| 4 | 0 | 3 | 1 | ## Common -### Objects - - ### Interfaces diff --git a/api_docs/kbn_core_node_server_mocks.devdocs.json b/api_docs/kbn_core_node_server_mocks.devdocs.json index f2d7ca94bf8cf..7997492fa90e4 100644 --- a/api_docs/kbn_core_node_server_mocks.devdocs.json +++ b/api_docs/kbn_core_node_server_mocks.devdocs.json @@ -76,7 +76,7 @@ "label": "createInternalStartContract", "description": [], "signature": [ - "({ ui, backgroundTasks, }?: { ui: boolean; backgroundTasks: boolean; }) => jest.Mocked<", + "({ ui, backgroundTasks, migrator, }?: { ui: boolean; backgroundTasks: boolean; migrator: boolean; }) => jest.Mocked<", { "pluginId": "@kbn/core-node-server-internal", "scope": "common", @@ -99,7 +99,7 @@ "label": "__0", "description": [], "signature": [ - "{ ui: boolean; backgroundTasks: boolean; }" + "{ ui: boolean; backgroundTasks: boolean; migrator: boolean; }" ], "path": "packages/core/node/core-node-server-mocks/src/node_service.mock.ts", "deprecated": false, diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index af63729ff7af0..feb9dc2ab14ec 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-27 +date: 2023-03-01 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 f232dc1095c47..c3932ab62690f 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-27 +date: 2023-03-01 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 704727e17650c..9ed261b31e520 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-27 +date: 2023-03-01 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 7302144640fee..53901c99c26eb 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-27 +date: 2023-03-01 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 f3f74033f0690..7ef2add6594c3 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-27 +date: 2023-03-01 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 dae2144fd0091..c5b2275067509 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-27 +date: 2023-03-01 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 bde023a91ebb5..91222ce58d1f3 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-27 +date: 2023-03-01 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 36b8d050f77d5..9f893da6f4cdb 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-27 +date: 2023-03-01 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 019e64ab0b420..ac6c24d330cd0 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-27 +date: 2023-03-01 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 bff0a2571ea11..c77bdc341cbae 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-27 +date: 2023-03-01 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 e9e97ad4ac7ba..ea41b943c5e51 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-27 +date: 2023-03-01 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 be617032639b4..26c8e4e5cd2c1 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-27 +date: 2023-03-01 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 e99e6c1cb1a3e..d2a828db81119 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-27 +date: 2023-03-01 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 fd4876fb7217e..2d0cf533be77e 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-27 +date: 2023-03-01 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 05083a9379a4d..da0ab5e88b51e 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-27 +date: 2023-03-01 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 9a1b0c18751f7..a7a23df24f99c 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_core_root_server_internal.devdocs.json index 765f11e6ad76e..c5af081041ed3 100644 --- a/api_docs/kbn_core_root_server_internal.devdocs.json +++ b/api_docs/kbn_core_root_server_internal.devdocs.json @@ -417,7 +417,43 @@ "initialIsOpen": false } ], - "functions": [], + "functions": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-common.registerServiceConfig", + "type": "Function", + "tags": [], + "label": "registerServiceConfig", + "description": [], + "signature": [ + "(configService: ", + "ConfigService", + ") => void" + ], + "path": "packages/core/root/core-root-server-internal/src/register_service_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-common.registerServiceConfig.$1", + "type": "Object", + "tags": [], + "label": "configService", + "description": [], + "signature": [ + "ConfigService" + ], + "path": "packages/core/root/core-root-server-internal/src/register_service_config.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [], "misc": [], diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index cc1550490e12f..14d1c29129edb 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 23 | 1 | 22 | 0 | +| 25 | 1 | 24 | 0 | ## Common +### Functions + + ### Classes 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..a7830aa795a0b 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -2243,10 +2243,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" @@ -4000,50 +3996,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 5a6fe5d1dc482..b234e165f5193 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-27 +date: 2023-03-01 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 3eedd0bf046a0..5ac110837d25a 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-27 +date: 2023-03-01 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 26e8db451e83a..a7883f71673c9 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-27 +date: 2023-03-01 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 eec7b798a06c1..6744441152126 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json index 89b711814980d..7e50fa4187ed5 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json @@ -70,7 +70,7 @@ "label": "migration", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -129,7 +129,7 @@ "label": "rawMigrationConfig", "description": [], "signature": [ - "Readonly<{ discardUnknownObjects?: string | undefined; discardCorruptObjects?: string | undefined; } & { skip: boolean; pollInterval: number; batchSize: number; maxBatchSizeBytes: ", + "Readonly<{ discardUnknownObjects?: string | undefined; discardCorruptObjects?: string | undefined; } & { skip: boolean; pollInterval: number; algorithm: \"v2\" | \"zdt\"; batchSize: number; maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -755,7 +755,7 @@ "label": "SavedObjectsMigrationConfigType", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -887,7 +887,15 @@ "section": "def-common.ObjectType", "text": "ObjectType" }, - "<{ batchSize: ", + "<{ algorithm: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "<\"v2\" | \"zdt\">; batchSize: ", { "pluginId": "@kbn/config-schema", "scope": "common", 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 1f86a3cecacdc..f9423495a45b7 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-27 +date: 2023-03-01 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 d36afdadf0cbd..e40ed8d0d733d 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-27 +date: 2023-03-01 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 36259efd2ef5a..688d1d555bec8 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-27 +date: 2023-03-01 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 f84981449ddf2..8713804799d47 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-27 +date: 2023-03-01 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 5228ef48d73d9..48afdf8837561 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-27 +date: 2023-03-01 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.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 5db946fd1fc84..a335192d95087 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-27 +date: 2023-03-01 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 66ce8098c17c7..3f22fd666b55c 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-27 +date: 2023-03-01 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 e7695de2d2b2f..539a0fc832619 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-27 +date: 2023-03-01 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 68bab1e33dbd5..0134af64ddd5c 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 @@ -599,6 +599,57 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.buildTypesMappings", + "type": "Function", + "tags": [], + "label": "buildTypesMappings", + "description": [ + "\nMerge mappings from all registered saved object types." + ], + "signature": [ + "(types: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]) => ", + "SavedObjectsTypeMappingDefinitions" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.buildTypesMappings.$1", + "type": "Array", + "tags": [], + "label": "types", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.bulkOverwriteTransformedDocuments", @@ -609,7 +660,7 @@ "\nWrite the up-to-date transformed documents to the index, overwriting any\ndocuments that are still on their outdated version." ], "signature": [ - "({ client, index, transformedDocs, refresh, }: ", + "({ client, index, operations, refresh, }: ", "BulkOverwriteTransformedDocumentsParams", ") => ", "TaskEither", @@ -632,7 +683,7 @@ "id": "def-common.bulkOverwriteTransformedDocuments.$1", "type": "Object", "tags": [], - "label": "{\n client,\n index,\n transformedDocs,\n refresh = false,\n }", + "label": "{\n client,\n index,\n operations,\n refresh = false,\n }", "description": [], "signature": [ "BulkOverwriteTransformedDocumentsParams" @@ -816,6 +867,92 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkDeleteOperationBody", + "type": "Function", + "tags": [], + "label": "createBulkDeleteOperationBody", + "description": [ + "\nGiven a document id, creates a valid body to delete the document using the Bulk API." + ], + "signature": [ + "(_id: string) => ", + "BulkOperationContainer" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkDeleteOperationBody.$1", + "type": "string", + "tags": [], + "label": "_id", + "description": [], + "signature": [ + "string" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkIndexOperationTuple", + "type": "Function", + "tags": [], + "label": "createBulkIndexOperationTuple", + "description": [ + "\nGiven a document, creates a valid body to index the document using the Bulk API." + ], + "signature": [ + "(doc: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsRawDoc", + "text": "SavedObjectsRawDoc" + }, + ") => ", + "BulkIndexOperationTuple" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkIndexOperationTuple.$1", + "type": "Object", + "tags": [], + "label": "doc", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsRawDoc", + "text": "SavedObjectsRawDoc" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.createIndex", @@ -1091,57 +1228,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.mergeTypes", - "type": "Function", - "tags": [], - "label": "mergeTypes", - "description": [ - "\nMerges savedObjectMappings properties into a single object, verifying that\nno mappings are redefined." - ], - "signature": [ - "(types: ", - { - "pluginId": "@kbn/core-saved-objects-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsServerPluginApi", - "section": "def-common.SavedObjectsType", - "text": "SavedObjectsType" - }, - "[]) => ", - "SavedObjectsTypeMappingDefinitions" - ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.mergeTypes.$1", - "type": "Array", - "tags": [], - "label": "types", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-saved-objects-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsServerPluginApi", - "section": "def-common.SavedObjectsType", - "text": "SavedObjectsType" - }, - "[]" - ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.openPit", @@ -3335,7 +3421,7 @@ "label": "soMigrationsConfig", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", 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 aaa6da1d1ce75..9b334a940a32d 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.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 | |-------------------|-----------|------------------------|-----------------| -| 112 | 0 | 79 | 45 | +| 116 | 0 | 81 | 46 | ## Common 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 190de89e284e3..29cea64b32846 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-27 +date: 2023-03-01 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 1997dd7e500a1..eca87703c355f 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-27 +date: 2023-03-01 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 ad88d3434c93b..c8af573bde495 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-27 +date: 2023-03-01 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 e019befaf4778..7c2ec8a7a83ee 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-27 +date: 2023-03-01 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 a3cdd465231a2..e210a1228ec23 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-27 +date: 2023-03-01 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 db13569fc15a5..a11aed7d32d8a 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-27 +date: 2023-03-01 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 e98ac07a6eb86..d7e00df6fc8a3 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-27 +date: 2023-03-01 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 184613b2bd134..cb0d5036718ed 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-27 +date: 2023-03-01 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 8ee1bcb32622d..0a96f49ffa4f6 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-27 +date: 2023-03-01 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 a72c75b0a892c..327e3a271a3dd 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-27 +date: 2023-03-01 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 e16d7583efcd8..c96e4fd0af6a1 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-27 +date: 2023-03-01 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 1a08b1250ecf2..096a5cf9c010d 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-27 +date: 2023-03-01 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 7fc2878ad1206..fb1f6004cad61 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-27 +date: 2023-03-01 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 eda623498db60..5bdb15b6bf91c 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-27 +date: 2023-03-01 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 447c495ff4ccd..698eacb03771d 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-27 +date: 2023-03-01 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 175e1ed9bab85..54165d1e0c654 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-27 +date: 2023-03-01 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 35c6273c61213..9ef8f2333fe52 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-27 +date: 2023-03-01 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 3b22b27f1997a..4d0b8b852b174 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-27 +date: 2023-03-01 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 f3406378d00d4..fc433f2993985 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-27 +date: 2023-03-01 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 76e920393bb73..60bdeaf388128 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-27 +date: 2023-03-01 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 66666a05ffe19..aba1f92ba7723 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-27 +date: 2023-03-01 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 927468797587b..a5e94c54239ea 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-27 +date: 2023-03-01 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 71cb58aad9b56..7bb50d06aebc7 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-27 +date: 2023-03-01 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 56d22396db1b6..3c1d1da90502f 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-27 +date: 2023-03-01 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 78bad8c43e148..db46d9394da82 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-27 +date: 2023-03-01 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 9c2262467058a..253d2892a934b 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-27 +date: 2023-03-01 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 588d770ab0f57..da766dfd397e6 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-27 +date: 2023-03-01 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 d5dda260a906e..fd300908ba5ff 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-27 +date: 2023-03-01 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 02386cdf929e1..611326ca0ca1b 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-27 +date: 2023-03-01 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 d7193bced5849..b67b17f6a2b76 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-27 +date: 2023-03-01 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 53c2e496b6b2e..6b16b88b54504 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-27 +date: 2023-03-01 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 2e5a072cf7495..bcf02b08bbd13 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-27 +date: 2023-03-01 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 b08d3e2f1a0eb..9608ba018aa9b 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-27 +date: 2023-03-01 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 658fd80529198..8ae612c0cd7cf 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-27 +date: 2023-03-01 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 1513bbc31f7a0..a620da438f055 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-27 +date: 2023-03-01 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 5e9fb3d83fde2..6e9803ca19ca3 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-27 +date: 2023-03-01 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 415e48f690346..5c26c1f7ae912 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-27 +date: 2023-03-01 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 a9f89a9888a86..92321620ae072 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-27 +date: 2023-03-01 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 21787f427307a..86c05cddc426a 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-27 +date: 2023-03-01 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 552ef1cd7001e..833dce59d69af 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-27 +date: 2023-03-01 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 5c8cc814ac584..b1052fab18d8a 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-27 +date: 2023-03-01 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 fecb21184b8a1..f2edd04564e87 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-27 +date: 2023-03-01 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 784763e15d41d..25691edd038ab 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-27 +date: 2023-03-01 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 ba77b55460365..a775b77d0c03b 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-27 +date: 2023-03-01 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 8234d3212999f..40d30548e2c75 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-27 +date: 2023-03-01 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 273601e75d946..53f09c9a56f21 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-27 +date: 2023-03-01 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 eaa59a2eea258..c755e7e49ec70 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_expandable_flyout.devdocs.json new file mode 100644 index 0000000000000..9658f381fd343 --- /dev/null +++ b/api_docs/kbn_expandable_flyout.devdocs.json @@ -0,0 +1,273 @@ +{ + "id": "@kbn/expandable-flyout", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyout", + "type": "Function", + "tags": [], + "label": "ExpandableFlyout", + "description": [ + "\nExpandable flyout UI React component.\nDisplays 3 sections (right, left, preview) depending on the panels in the context." + ], + "signature": [ + "{ ({ registeredPanels, handleOnFlyoutClosed, ...flyoutProps }: React.PropsWithChildren<", + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + ">): JSX.Element; displayName: string | undefined; }" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyout.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n registeredPanels,\n handleOnFlyoutClosed,\n ...flyoutProps\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + ">" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProvider", + "type": "Function", + "tags": [], + "label": "ExpandableFlyoutProvider", + "description": [ + "\nWrap your plugin with this context for the ExpandableFlyout React component." + ], + "signature": [ + "({ children }: ", + "ExpandableFlyoutProviderProps", + ") => JSX.Element" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProvider.$1", + "type": "Object", + "tags": [], + "label": "{ children }", + "description": [], + "signature": [ + "ExpandableFlyoutProviderProps" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.useExpandableFlyoutContext", + "type": "Function", + "tags": [], + "label": "useExpandableFlyoutContext", + "description": [ + "\nRetrieve context's properties" + ], + "signature": [ + "() => ", + "ExpandableFlyoutContext" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps", + "type": "Interface", + "tags": [], + "label": "ExpandableFlyoutProps", + "description": [], + "signature": [ + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + " extends ", + "EuiFlyoutProps", + "<\"div\">" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps.registeredPanels", + "type": "Array", + "tags": [], + "label": "registeredPanels", + "description": [ + "\nList of all registered panels available for render" + ], + "signature": [ + "Panel", + "[]" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps.handleOnFlyoutClosed", + "type": "Function", + "tags": [], + "label": "handleOnFlyoutClosed", + "description": [ + "\nPropagate out EuiFlyout onClose event" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel", + "type": "Interface", + "tags": [], + "label": "FlyoutPanel", + "description": [], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique key to identify the panel" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.params", + "type": "Object", + "tags": [], + "label": "params", + "description": [ + "\nAny parameters necessary for the initial requests within the flyout" + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.path", + "type": "Array", + "tags": [], + "label": "path", + "description": [ + "\nTracks the path for what to show in a panel. We may have multiple tabs or details..., so easiest to just use a stack" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.state", + "type": "Object", + "tags": [], + "label": "state", + "description": [ + "\nTracks visual state such as whether the panel is collapsed" + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx new file mode 100644 index 0000000000000..8cd64f4bf5007 --- /dev/null +++ b/api_docs/kbn_expandable_flyout.mdx @@ -0,0 +1,33 @@ +--- +#### +#### 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: kibKbnExpandableFlyoutPluginApi +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-03-01 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] +--- +import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; + + + +Contact [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 13 | 0 | 4 | 3 | + +## Common + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 0287d21eb53df..882ddbccdafc2 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-27 +date: 2023-03-01 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 8d3dbec5ef1d4..9408b6512bfc6 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-27 +date: 2023-03-01 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 17c629d017633..7aa6edd01eaed 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-27 +date: 2023-03-01 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 aee0d8aa6d524..c96a7fec26b54 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-27 +date: 2023-03-01 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 27c485203f114..41402fb54e395 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.devdocs.json b/api_docs/kbn_handlebars.devdocs.json index 0c3d26cf1c175..84069ecfa5b9c 100644 --- a/api_docs/kbn_handlebars.devdocs.json +++ b/api_docs/kbn_handlebars.devdocs.json @@ -420,7 +420,7 @@ "\nSupported Handlebars compile options.\n\nThis is a subset of all the compile options supported by the upstream\nHandlebars module." ], "signature": [ - "{ data?: boolean | undefined; strict?: boolean | undefined; knownHelpers?: KnownHelpers | undefined; knownHelpersOnly?: boolean | undefined; noEscape?: boolean | undefined; assumeObjects?: boolean | undefined; preventIndent?: boolean | undefined; explicitPartialContext?: boolean | undefined; }" + "{ strict?: boolean | undefined; data?: boolean | undefined; knownHelpers?: KnownHelpers | undefined; knownHelpersOnly?: boolean | undefined; noEscape?: boolean | undefined; assumeObjects?: boolean | undefined; preventIndent?: boolean | undefined; explicitPartialContext?: boolean | undefined; }" ], "path": "packages/kbn-handlebars/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index adb6e8aa995fd..5892aaecb2692 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-27 +date: 2023-03-01 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 508e8edcf7812..58903a768057d 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-27 +date: 2023-03-01 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 65fe99bcf0fd6..638f50b6e417a 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-27 +date: 2023-03-01 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 cb682ae4f9900..f6f8c68a13a53 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-27 +date: 2023-03-01 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 9d5c6575fdd70..a89d578374865 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-27 +date: 2023-03-01 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 093991e202a75..dcd9cecd5658a 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-27 +date: 2023-03-01 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 f3147c2dfe769..625e6d1fc8700 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-27 +date: 2023-03-01 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 021576539dda5..d724067993dff 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-27 +date: 2023-03-01 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 9a26721488b93..6d236d56a476c 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-27 +date: 2023-03-01 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 2f4a023579df9..60f0704834cb7 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-27 +date: 2023-03-01 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 a3c25d75910b4..901358d2ad029 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-27 +date: 2023-03-01 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 78c93dc47e995..8be26edb38791 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-27 +date: 2023-03-01 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 23162fdbcb8a0..7cf30e73b07e4 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-27 +date: 2023-03-01 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 7ab510e97ac36..5b32cf0eab32b 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-27 +date: 2023-03-01 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 5a4aac9574156..000ee3800cb54 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-27 +date: 2023-03-01 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 406cfb327294e..e0814e0c41bce 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-27 +date: 2023-03-01 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 8aa1631ce53a0..28ad3c57ee2b1 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-27 +date: 2023-03-01 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 c13e84f344705..9e6c619a43f72 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-27 +date: 2023-03-01 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 53359db3b93a4..2b6014f157459 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-27 +date: 2023-03-01 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 8bb33f2663dc2..82fb3b8156135 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 2f2cd5efe78bc..e22d099c43982 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index ebd371fa39360..7106fef0d31ec 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-27 +date: 2023-03-01 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 1d634f80ec520..da6012691ecbe 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-27 +date: 2023-03-01 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 1691190226d46..e2ac91904af64 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-27 +date: 2023-03-01 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 bcee4b75b6c2a..76e448217a11e 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-27 +date: 2023-03-01 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 09ed1ad02393c..1aaaba5d9d1b1 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-27 +date: 2023-03-01 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 f9c4d29869a7f..b605bd7a7d0c1 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-27 +date: 2023-03-01 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 d804c533e1b09..b58e96af08251 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-27 +date: 2023-03-01 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 3638a55e2ad02..0b3b1dcbb8d71 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-27 +date: 2023-03-01 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 8f216afa89398..6b8831494e1fc 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-27 +date: 2023-03-01 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 5de6feece7499..fefd5513c3a89 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-27 +date: 2023-03-01 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 1701080c152a4..0daafacb70363 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-27 +date: 2023-03-01 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 c89cd61bfdc71..135064793ced7 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-27 +date: 2023-03-01 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 db2b66505446a..2e7737d3d09a9 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-27 +date: 2023-03-01 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 3f547dc9a9278..1b0874efd96a0 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-27 +date: 2023-03-01 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 ab6126edfa1d0..e626443c3a86c 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-27 +date: 2023-03-01 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 2ce4e25fe73dd..97cf561b76dbe 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-27 +date: 2023-03-01 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 27117caad1c2a..bb83af20d577b 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-27 +date: 2023-03-01 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 07dcc95444a77..ed3fd24bf1ca0 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-27 +date: 2023-03-01 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 da2e98c4fb0fa..09b2b34589146 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-27 +date: 2023-03-01 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 1656d07cb758c..50073a95415d4 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 51429bd28d4a4..0b4f41adabbdc 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -218,7 +218,7 @@ "signature": [ "\"kibana.alert.case_ids\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -300,13 +300,13 @@ }, { "parentPluginId": "@kbn/rule-data-utils", - "id": "def-common.ALERT_ID", + "id": "def-common.ALERT_FLAPPING_HISTORY", "type": "string", "tags": [], - "label": "ALERT_ID", + "label": "ALERT_FLAPPING_HISTORY", "description": [], "signature": [ - "\"kibana.alert.id\"" + "\"kibana.alert.flapping_history\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, @@ -323,7 +323,7 @@ "signature": [ "\"kibana.alert.instance.id\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -383,7 +383,7 @@ "signature": [ "\"kibana.alert.risk_score\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -398,7 +398,7 @@ "signature": [ "\"kibana.alert.rule.author\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -443,7 +443,7 @@ "signature": [ "\"kibana.alert.rule.created_at\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -458,7 +458,7 @@ "signature": [ "\"kibana.alert.rule.created_by\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -473,7 +473,7 @@ "signature": [ "\"kibana.alert.rule.description\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -488,7 +488,7 @@ "signature": [ "\"kibana.alert.rule.enabled\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -533,7 +533,7 @@ "signature": [ "\"kibana.alert.rule.from\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -548,7 +548,7 @@ "signature": [ "\"kibana.alert.rule.interval\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -563,7 +563,7 @@ "signature": [ "\"kibana.alert.rule.license\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -623,7 +623,7 @@ "signature": [ "\"kibana.alert.rule.note\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -668,7 +668,7 @@ "signature": [ "\"kibana.alert.rule.references\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -683,7 +683,7 @@ "signature": [ "\"kibana.alert.rule.rule_id\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -698,7 +698,7 @@ "signature": [ "\"kibana.alert.rule.rule_name_override\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -728,7 +728,7 @@ "signature": [ "\"kibana.alert.rule.to\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -743,7 +743,7 @@ "signature": [ "\"kibana.alert.rule.type\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -773,7 +773,7 @@ "signature": [ "\"kibana.alert.rule.updated_at\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -788,7 +788,7 @@ "signature": [ "\"kibana.alert.rule.updated_by\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -818,7 +818,7 @@ "signature": [ "\"kibana.alert.rule.version\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -833,7 +833,7 @@ "signature": [ "\"kibana.alert.severity\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -938,7 +938,7 @@ "signature": [ "\"kibana.alert.suppression.docs_count\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -953,7 +953,7 @@ "signature": [ "\"kibana.alert.suppression.end\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -968,7 +968,7 @@ "signature": [ "\"kibana.alert.suppression.terms.field\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -983,7 +983,7 @@ "signature": [ "\"kibana.alert.suppression.start\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -998,7 +998,7 @@ "signature": [ "\"kibana.alert.suppression.terms\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1013,7 +1013,7 @@ "signature": [ "\"kibana.alert.suppression.terms.value\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1028,7 +1028,7 @@ "signature": [ "\"kibana.alert.system_status\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1223,7 +1223,7 @@ "signature": [ "\"kibana.alert.workflow_reason\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1253,7 +1253,7 @@ "signature": [ "\"kibana.alert.workflow_user\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1311,7 +1311,7 @@ "label": "DefaultAlertFieldName", "description": [], "signature": [ - "\"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"kibana.alert.workflow_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.tags\" | \"kibana.alert.last_detected\" | \"kibana.alert.id\"" + "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.instance.id\" | \"kibana.alert.last_detected\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, @@ -1328,7 +1328,7 @@ "signature": [ "\"ecs.version\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1343,7 +1343,7 @@ "signature": [ "\"event.action\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1358,7 +1358,7 @@ "signature": [ "\"event.kind\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1463,7 +1463,7 @@ "signature": [ "\"tags\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1476,7 +1476,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.case_ids\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.docs_count\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" + "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.instance.id\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.severity\" | \"kibana.alert.suppression.docs_count\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.system_status\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.workflow_user\" | \"ecs.version\" | \"event.kind\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"event.module\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" ], "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", "deprecated": false, @@ -1493,7 +1493,7 @@ "signature": [ "\"@timestamp\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 24bbcf7806d47..95ddbf97b66e6 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-27 +date: 2023-03-01 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 280afbc3028e4..f3b12dba64e8f 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-27 +date: 2023-03-01 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 b52227497982a..8cfec1a146f1f 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-27 +date: 2023-03-01 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 5e2dbdeb7c034..e2d46df9fa528 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-27 +date: 2023-03-01 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 d610a4cd3eb85..f65f4b5d15f06 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-27 +date: 2023-03-01 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 50ca1b14e44a3..6db5d2665ed2d 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-27 +date: 2023-03-01 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 dcb989b2881ad..4fa8e0495146f 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 35b7b45d54e6f..dcf633ba8463d 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-27 +date: 2023-03-01 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 085842ffd707e..24f4035a6e10f 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-27 +date: 2023-03-01 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 7e355cc10b8dc..115cb421ce21e 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-27 +date: 2023-03-01 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 cd458e3bd8070..1bdb1d69ada89 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-27 +date: 2023-03-01 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 27628ddca800f..666f7d64dc4c0 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-27 +date: 2023-03-01 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 b33fa79e75be6..10a5bd75e918e 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-27 +date: 2023-03-01 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 85723f920c3b1..4f279c8da7457 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-27 +date: 2023-03-01 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 8d1de91435814..21c514be81803 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-27 +date: 2023-03-01 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 58f76d4772be0..30d9b478e5fb2 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-27 +date: 2023-03-01 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 919c81d834263..a9e0ce333f4ac 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-27 +date: 2023-03-01 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 e069f4f2a258f..b75e7bd174f09 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-27 +date: 2023-03-01 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 c1ea8d74f6da6..4608fd2d9fae0 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-27 +date: 2023-03-01 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 4673569b1f4e1..dbed2431570ba 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-27 +date: 2023-03-01 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 bc065cf2a4cba..49369a523c57d 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-27 +date: 2023-03-01 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 d553b227b1143..de603c176f88d 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-27 +date: 2023-03-01 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 4a39bf9fb3c9e..6dc92cb308995 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-27 +date: 2023-03-01 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 c7deece2ea113..d18090ddcfca4 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-27 +date: 2023-03-01 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 fa35a43514eeb..d5438a3b81231 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-27 +date: 2023-03-01 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 3586f053ac47f..e7f87b3faead1 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-27 +date: 2023-03-01 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 2e1bffe344d14..bbc60b7b0930e 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_context.devdocs.json index 02025b778807c..a48e1addf9ede 100644 --- a/api_docs/kbn_shared_ux_file_context.devdocs.json +++ b/api_docs/kbn_shared_ux_file_context.devdocs.json @@ -99,7 +99,13 @@ "\nA files client that will be used process uploads." ], "signature": [ - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, "" ], "path": "packages/shared-ux/file/context/src/index.tsx", diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index bae037be686b5..f96d798bfdd70 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_image.devdocs.json index 5d3350f6cf444..4dfade1406167 100644 --- a/api_docs/kbn_shared_ux_file_image.devdocs.json +++ b/api_docs/kbn_shared_ux_file_image.devdocs.json @@ -83,7 +83,13 @@ "description": [], "signature": [ "{ meta?: ", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined; } & ", "EuiImageProps" ], diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 4dffa9c1aef61..6ed126b1f0272 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-27 +date: 2023-03-01 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 69bfe48a537c0..5059a2104ecc6 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_mocks.devdocs.json index ecdb910452dba..1a16eeed01e56 100644 --- a/api_docs/kbn_shared_ux_file_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_file_mocks.devdocs.json @@ -36,7 +36,13 @@ "text": "DeeplyMockedKeys" }, "<", - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, ">" ], "path": "packages/shared-ux/file/mocks/index.ts", diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 6ffefddd67b1e..99a891466643c 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_picker.devdocs.json index 0838853073257..1ecf0fc909e4e 100644 --- a/api_docs/kbn_shared_ux_file_picker.devdocs.json +++ b/api_docs/kbn_shared_ux_file_picker.devdocs.json @@ -117,7 +117,13 @@ ], "signature": [ "((file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, ") => boolean) | undefined" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -132,7 +138,13 @@ "label": "file", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -172,7 +184,13 @@ ], "signature": [ "(files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]) => void" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -187,7 +205,13 @@ "label": "files", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index c84800f82883a..48e442ff2f4a7 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_types.devdocs.json new file mode 100644 index 0000000000000..de6c162159d55 --- /dev/null +++ b/api_docs/kbn_shared_ux_file_types.devdocs.json @@ -0,0 +1,1581 @@ +{ + "id": "@kbn/shared-ux-file-types", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Abortable", + "type": "Interface", + "tags": [], + "label": "Abortable", + "description": [], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Abortable.abortSignal", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient", + "type": "Interface", + "tags": [], + "label": "BaseFilesClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, + "" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.find", + "type": "Function", + "tags": [], + "label": "find", + "description": [ + "\nFind a set of files given some filters.\n" + ], + "signature": [ + "(args: { kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ files: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]; total: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.find.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File filters" + ], + "signature": [ + "{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nBulk a delete a set of files given their IDs.\n" + ], + "signature": [ + "(args: { ids: string[]; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.bulkDelete.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Bulk delete args" + ], + "signature": [ + "{ ids: string[]; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [ + "\nCreate a new file object with the provided metadata.\n" + ], + "signature": [ + "(args: { name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.create.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- create file args" + ], + "signature": [ + "{ name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [ + "\nDelete a file object and all associated share and content objects.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.delete.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- delete file args" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getById", + "type": "Function", + "tags": [], + "label": "getById", + "description": [ + "\nGet a file object by ID.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getById.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- get file by ID args" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.list", + "type": "Function", + "tags": [], + "label": "list", + "description": [ + "\nList all file objects, of a given {@link FileKindBrowser}.\n" + ], + "signature": [ + "(args: { kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ files: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]; total: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.list.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- list files args" + ], + "signature": [ + "{ kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [ + "\nUpdate a set of of metadata values of the file object.\n" + ], + "signature": [ + "(args: { id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.update.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- update file args" + ], + "signature": [ + "{ id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.upload", + "type": "Function", + "tags": [], + "label": "upload", + "description": [ + "\nStream the contents of the file to Kibana server for storage.\n" + ], + "signature": [ + "(args: { id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; size: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.upload.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- upload file args" + ], + "signature": [ + "{ id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.download", + "type": "Function", + "tags": [], + "label": "download", + "description": [ + "\nStream a download of the file object's content.\n" + ], + "signature": [ + "(args: { fileName?: string | undefined; id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.download.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- download file args" + ], + "signature": [ + "{ fileName?: string | undefined; id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getDownloadHref", + "type": "Function", + "tags": [], + "label": "getDownloadHref", + "description": [ + "\nGet a string for downloading a file that can be passed to a button element's\nhref for download.\n" + ], + "signature": [ + "(args: Pick<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ", \"id\" | \"fileKind\">) => string" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getDownloadHref.$1", + "type": "Object", + "tags": [], + "label": "args", + "description": [ + "- get download URL args" + ], + "signature": [ + "Pick<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ", \"id\" | \"fileKind\">" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.share", + "type": "Function", + "tags": [ + "note" + ], + "label": "share", + "description": [ + "\nShare a file by creating a new file share instance.\n" + ], + "signature": [ + "(args: { name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSONWithToken", + "text": "FileShareJSONWithToken" + }, + ">" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.share.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File share arguments" + ], + "signature": [ + "{ name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.unshare", + "type": "Function", + "tags": [], + "label": "unshare", + "description": [ + "\nDelete a file share instance.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.unshare.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File unshare arguments" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getShare", + "type": "Function", + "tags": [], + "label": "getShare", + "description": [ + "\nGet a file share instance.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getShare.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Get file share arguments" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.listShares", + "type": "Function", + "tags": [], + "label": "listShares", + "description": [ + "\nList all file shares. Optionally scoping to a specific\nfile.\n" + ], + "signature": [ + "(args: { forFileId?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ shares: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.listShares.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Get file share arguments" + ], + "signature": [ + "{ forFileId?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getFileKind", + "type": "Function", + "tags": [], + "label": "getFileKind", + "description": [ + "\nGet a file kind" + ], + "signature": [ + "(id: string) => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getFileKind.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "The id of the file kind" + ], + "signature": [ + "string" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata", + "type": "Interface", + "tags": [], + "label": "FileImageMetadata", + "description": [ + "\nSet of metadata captured for every image uploaded via the file services'\npublic components." + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.blurhash", + "type": "string", + "tags": [], + "label": "blurhash", + "description": [ + "\nThe blurhash that can be displayed while the image is loading" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.width", + "type": "number", + "tags": [], + "label": "width", + "description": [ + "\nWidth, in px, of the original image" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.height", + "type": "number", + "tags": [], + "label": "height", + "description": [ + "\nHeight, in px, of the original image" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON", + "type": "Interface", + "tags": [], + "label": "FileJSON", + "description": [ + "\nAttributes of a file that represent a serialised version of the file." + ], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique file ID." + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.created", + "type": "string", + "tags": [], + "label": "created", + "description": [ + "\nISO string of when this file was created" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.updated", + "type": "string", + "tags": [], + "label": "updated", + "description": [ + "\nISO string of when the file was updated" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.name", + "type": "string", + "tags": [ + "note" + ], + "label": "name", + "description": [ + "\nFile name.\n" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.mimeType", + "type": "string", + "tags": [], + "label": "mimeType", + "description": [ + "\nMIME type of the file's contents." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.size", + "type": "number", + "tags": [], + "label": "size", + "description": [ + "\nThe size, in bytes, of the file content." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.extension", + "type": "string", + "tags": [ + "note" + ], + "label": "extension", + "description": [ + "\nThe file extension (dot suffix).\n" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.meta", + "type": "Uncategorized", + "tags": [], + "label": "meta", + "description": [ + "\nA consumer defined set of attributes.\n\nConsumers of the file service can add their own tags and identifiers to\na file using the \"meta\" object." + ], + "signature": [ + "Meta | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.alt", + "type": "string", + "tags": [], + "label": "alt", + "description": [ + "\nUse this text to describe the file contents for display and accessibility." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.fileKind", + "type": "string", + "tags": [ + "note" + ], + "label": "fileKind", + "description": [ + "\nA unique kind that governs various aspects of the file. A consumer of the\nfiles service must register a file kind and link their files to a specific\nkind.\n" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.status", + "type": "CompoundType", + "tags": [], + "label": "status", + "description": [ + "\nThe current status of the file.\n\nSee {@link FileStatus} for more details." + ], + "signature": [ + "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [ + "\nUser data associated with this file" + ], + "signature": [ + "{ name?: string | undefined; id?: string | undefined; } | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase", + "type": "Interface", + "tags": [], + "label": "FileKindBase", + "description": [], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique file kind ID" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase.allowedMimeTypes", + "type": "Array", + "tags": [ + "default" + ], + "label": "allowedMimeTypes", + "description": [ + "\nThe MIME type of the file content.\n" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBrowser", + "type": "Interface", + "tags": [], + "label": "FileKindBrowser", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBrowser.maxSizeBytes", + "type": "number", + "tags": [ + "default" + ], + "label": "maxSizeBytes", + "description": [ + "\nMax file contents size, in bytes, enforced for this file kind in the upload\ncomponent.\n" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON", + "type": "Interface", + "tags": [], + "label": "FileShareJSON", + "description": [ + "\nAttributes of a file that represent a serialised version of the file." + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique ID share instance" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.created", + "type": "string", + "tags": [], + "label": "created", + "description": [ + "\nISO timestamp the share was created" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.validUntil", + "type": "number", + "tags": [], + "label": "validUntil", + "description": [ + "\nUnix timestamp (in milliseconds) of when this share expires" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "\nA user-friendly name for the file share" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.fileId", + "type": "string", + "tags": [], + "label": "fileId", + "description": [ + "\nThe ID of the file this share is linked to" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination", + "type": "Interface", + "tags": [], + "label": "Pagination", + "description": [], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination.page", + "type": "number", + "tags": [], + "label": "page", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination.perPage", + "type": "number", + "tags": [], + "label": "perPage", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFileMetadata", + "type": "Type", + "tags": [], + "label": "BaseFileMetadata", + "description": [ + "\nFile metadata fields are defined per the ECS specification:\n\nhttps://www.elastic.co/guide/en/ecs/current/ecs-file.html\n\nCustom fields are named according to the custom field convention: \"CustomFieldName\"." + ], + "signature": [ + "{ name?: string | undefined; mime_type?: string | undefined; created?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; Updated?: string | undefined; Status?: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileStatus", + "text": "FileStatus" + }, + " | undefined; ChunkSize?: number | undefined; Compression?: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, + " | undefined; }" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileCompression", + "type": "Type", + "tags": [], + "label": "FileCompression", + "description": [ + "\nSupported file compression algorithms" + ], + "signature": [ + "\"none\" | \"br\" | \"gzip\" | \"deflate\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileMetadata", + "type": "Type", + "tags": [], + "label": "FileMetadata", + "description": [ + "\nExtra metadata on a file object specific to Kibana implementation." + ], + "signature": [ + "Required> & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, + " & { FileKind: string; Meta?: Meta | undefined; }" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShare", + "type": "Type", + "tags": [], + "label": "FileShare", + "description": [ + "\nData stored with a file share object" + ], + "signature": [ + "{ created: string; token: string; name?: string | undefined; valid_until: number; }" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSONWithToken", + "type": "Type", + "tags": [ + "note" + ], + "label": "FileShareJSONWithToken", + "description": [ + "\nA version of the file share with a token included.\n" + ], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + " & { token: string; }" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileStatus", + "type": "Type", + "tags": [], + "label": "FileStatus", + "description": [], + "signature": [ + "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx new file mode 100644 index 0000000000000..9f65fa210e7b8 --- /dev/null +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -0,0 +1,33 @@ +--- +#### +#### 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: kibKbnSharedUxFileTypesPluginApi +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-03-01 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] +--- +import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; + + + +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 70 | 0 | 9 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_shared_ux_file_upload.devdocs.json b/api_docs/kbn_shared_ux_file_upload.devdocs.json index 143e11c571ef2..82ccfb8816f4f 100644 --- a/api_docs/kbn_shared_ux_file_upload.devdocs.json +++ b/api_docs/kbn_shared_ux_file_upload.devdocs.json @@ -119,7 +119,13 @@ "label": "fileJSON", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "packages/shared-ux/file/file_upload/impl/src/upload_state.ts", diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 613cc2d6800b4..017044bc1816e 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-27 +date: 2023-03-01 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.devdocs.json b/api_docs/kbn_shared_ux_file_util.devdocs.json index c471a74aeec8e..bf89b1d3e11a3 100644 --- a/api_docs/kbn_shared_ux_file_util.devdocs.json +++ b/api_docs/kbn_shared_ux_file_util.devdocs.json @@ -144,7 +144,13 @@ ], "signature": [ "(file: Blob | File) => Promise<", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined>" ], "path": "packages/shared-ux/file/util/src/image_metadata.ts", @@ -264,7 +270,13 @@ "description": [], "signature": [ "(file: Blob | File) => Promise<", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined>" ], "path": "packages/shared-ux/file/util/src/image_metadata.ts", diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 49cba5b2b6b35..da482330aa69f 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-27 +date: 2023-03-01 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 bc4cd4bb296af..dc7da9fdb3de0 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-27 +date: 2023-03-01 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 a1832fbd6b9d0..34433ea142c25 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-27 +date: 2023-03-01 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 69a7fadee3a7c..16a4ef53f43e0 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-27 +date: 2023-03-01 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 7d39cc8ff942d..cb9dd8a0c0be1 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-27 +date: 2023-03-01 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 4d299d8c283c8..5e2e601161b23 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-27 +date: 2023-03-01 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 1b96b08fa1505..c721d2065548a 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-27 +date: 2023-03-01 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 41e4b50335f26..32ccb3a824df6 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-27 +date: 2023-03-01 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 0b5f529b6fc2e..449c6cb561b0f 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-27 +date: 2023-03-01 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 c092d3a7327e9..8eb428b3ec1fd 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-27 +date: 2023-03-01 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 5d3e545e34a9a..34cc61f2f9c01 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-27 +date: 2023-03-01 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 e99ba437ea181..08aab682ce2e6 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-27 +date: 2023-03-01 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 e473873e76f9c..9824ac22ba569 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-27 +date: 2023-03-01 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 24adfbf9bab9a..cd3c126d99beb 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-27 +date: 2023-03-01 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 f374066c21e10..f2df637ff592a 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-27 +date: 2023-03-01 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 b8c62ed4bc494..93dda125813c3 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-27 +date: 2023-03-01 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 c84c8dd68cbdb..2c0e1ec941c8d 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-27 +date: 2023-03-01 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 6a617b21a10a9..7e2646fc8085a 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-27 +date: 2023-03-01 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 e7c675c73947d..2ace684381a43 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-27 +date: 2023-03-01 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 287c4f3b7c580..440066e3a8f57 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-27 +date: 2023-03-01 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 52347ab66f141..43b990ab9241f 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-27 +date: 2023-03-01 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 d8a7f1f9b7677..3ba733b898ba3 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-27 +date: 2023-03-01 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 570ef34b459c4..62b3ffd910949 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-27 +date: 2023-03-01 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 2d2152989cec8..2a7caa471b5ac 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-27 +date: 2023-03-01 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 3d2fba338fcd2..77215eafe5c39 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-27 +date: 2023-03-01 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 926afb71749c5..e123f38fb650b 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-27 +date: 2023-03-01 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 783c0f7c701e2..7f6765e55f8e4 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-27 +date: 2023-03-01 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 e053e5ac872b2..a4a057a5ea50a 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-27 +date: 2023-03-01 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 9f9241b66735f..a957674b23663 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-27 +date: 2023-03-01 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 b5bfc34bfffce..9614a088a92bc 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-27 +date: 2023-03-01 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 e2558983fbebe..c557e001a9435 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-27 +date: 2023-03-01 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 d843cfc415389..25eee237600e1 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-27 +date: 2023-03-01 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 557f0858d2f54..78ba4948685c5 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-27 +date: 2023-03-01 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 bf45f0616677f..91cffd61ed3b8 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-27 +date: 2023-03-01 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 8fd07a8394192..6a1a4743dd6e1 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-27 +date: 2023-03-01 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 30c08ee3d477b..aac73b858246a 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-27 +date: 2023-03-01 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 b7972e4cee4f9..4cc5120445c02 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-27 +date: 2023-03-01 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 8b66ee98cff48..fca8308cff965 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-27 +date: 2023-03-01 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 b21e5ef9bd09d..f6106da7ed875 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-27 +date: 2023-03-01 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 7434590f401f9..75a89bba49dca 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-27 +date: 2023-03-01 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 bf7caa13eda59..3ee6c92133ecc 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-27 +date: 2023-03-01 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 76e3f26b7df27..a5833d2b4ed60 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-27 +date: 2023-03-01 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 eb3497195d0ae..85dc335a2bb76 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-27 +date: 2023-03-01 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 070ac1c4a6068..5dc2d050d2806 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-27 +date: 2023-03-01 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 73eda7bda3a96..f70f533212865 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-27 +date: 2023-03-01 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 6bf0149dfebb0..3bed1d4269ddb 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-27 +date: 2023-03-01 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 2e54a482ecfa0..12c873652a03e 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-27 +date: 2023-03-01 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 072f6b890c97f..01d10baf1cbed 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-27 +date: 2023-03-01 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 ee4da3c15af13..3dc29d6ecc73e 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-27 +date: 2023-03-01 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 9ca9dc4debbbe..c7783c09c4649 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-27 +date: 2023-03-01 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 49b8bee6aea9c..34117f2a0bb22 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-27 +date: 2023-03-01 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 9ba9e133f0eba..65eee5cb0ce4a 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 9e7a02cf6a059..98ebb58f999bb 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index b8a6b1c234a32..94eda5f98ae19 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-27 +date: 2023-03-01 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 2de6fd928dbb4..f296fc984ecb4 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-27 +date: 2023-03-01 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 6ca708031eb71..f44c6c7d72960 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-27 +date: 2023-03-01 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 84d73e1cc383c..063b121d33eeb 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-27 +date: 2023-03-01 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 38b9c7b4b4a02..07694d7e71b2f 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-27 +date: 2023-03-01 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 56d67962cddb2..60334ad4da977 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-27 +date: 2023-03-01 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 cfad19cb77150..d64152c39b1ac 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-27 +date: 2023-03-01 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 4300df9620dfa..cb334f1c5899e 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-27 +date: 2023-03-01 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 f5423d859c0c8..cb927160b6980 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-27 +date: 2023-03-01 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 1229afb3755be..15c641bc7406a 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -4679,7 +4679,7 @@ "label": "format", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -4698,7 +4698,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -6236,7 +6236,7 @@ "label": "ObservabilityRuleTypeFormatter", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -6255,7 +6255,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index ad380f21e8a89..1e474ec45c730 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.devdocs.json b/api_docs/osquery.devdocs.json index 29b2b564836a6..6232998bcc0ea 100644 --- a/api_docs/osquery.devdocs.json +++ b/api_docs/osquery.devdocs.json @@ -262,7 +262,7 @@ "label": "osqueryCreateAction", "description": [], "signature": [ - "(payload: { agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; removed: boolean | undefined; snapshot: boolean | undefined; }[] | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }, alertData?: OutputOf> | undefined) => void" + "(payload: { agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; removed: boolean | undefined; snapshot: boolean | undefined; }[] | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }, alertData?: OutputOf> | undefined) => void" ], "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, @@ -291,7 +291,7 @@ "label": "alertData", "description": [], "signature": [ - "OutputOf> | undefined" + "OutputOf> | undefined" ], "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index b3e3702b98a01..876a6cbb91d01 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-27 +date: 2023-03-01 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 241f72e86a5f0..0258630d7d754 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-27 +date: 2023-03-01 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 | |--------------|----------|------------------------| -| 574 | 469 | 38 | +| 576 | 472 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 67704 | 515 | 58526 | 1238 | +| 67878 | 515 | 58596 | 1234 | ## Plugin Directory @@ -30,7 +30,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 256 | 8 | 251 | 24 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 36 | 1 | 32 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 12 | 0 | 1 | 2 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 487 | 1 | 476 | 40 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 497 | 1 | 486 | 41 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 42 | 0 | 42 | 65 | | | [@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 | @@ -40,14 +40,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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 | @@ -64,7 +64,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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 | 538 | 9 | 435 | 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 | @@ -88,9 +88,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 236 | 0 | 100 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 288 | 26 | 249 | 3 | | | [@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. | 254 | 1 | 45 | 5 | +| | [@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) | - | 1087 | 3 | 982 | 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 | @@ -137,7 +137,7 @@ 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-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) | - | 39 | 0 | 39 | 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 | @@ -199,6 +199,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 6 | 0 | 6 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 53 | 0 | 22 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 9 | 1 | 9 | 0 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 12 | 0 | 12 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 5 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 73 | 0 | 73 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 98 | 0 | 0 | 0 | @@ -323,8 +324,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 6 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 3 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 5 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 35 | 4 | 23 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 32 | 0 | 11 | 2 | @@ -341,7 +342,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 2 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 23 | 1 | 22 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 25 | 1 | 24 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 107 | 1 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 334 | 1 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 75 | 0 | 54 | 1 | @@ -354,7 +355,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 71 | 0 | 39 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 25 | 0 | 23 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 112 | 0 | 79 | 45 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 116 | 0 | 81 | 46 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 489 | 1 | 98 | 4 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 69 | 4 | @@ -402,6 +403,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 251 | 1 | 193 | 15 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 13 | 0 | 4 | 3 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 16 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 29 | 0 | 29 | 1 | @@ -480,6 +482,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 14 | 0 | 6 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 70 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 7 | 0 | 7 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 15 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 9 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 2e4c690bdfde2..10ac8aa29d17d 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-27 +date: 2023-03-01 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 4e7eff9bad9ea..78f89c756ed1a 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-27 +date: 2023-03-01 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 50384f02b70fd..dad0ad96c8b86 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-27 +date: 2023-03-01 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 d22bdb346e94b..a163a3e5f48fc 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-27 +date: 2023-03-01 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 9b051aa152030..695c084c67059 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index e9bed596ec7e7..e0588585ba405 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -107,7 +107,7 @@ "label": "get", "description": [], "signature": [ - "({ id, index }: GetAlertParams) => Promise> | undefined>" + "({ id, index }: GetAlertParams) => Promise> | undefined>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -325,7 +325,7 @@ "SortOptions", "[] | undefined; track_total_hits?: boolean | undefined; _source?: string[] | undefined; }) => Promise<", "SearchResponse", - ">, Record>, Record>>" ], @@ -1901,7 +1901,7 @@ "RuleTypeParamsValidator", " | undefined; } | undefined; cancelAlertsOnRuleTimeout?: boolean | undefined; alerts?: ", "IRuleTypeAlerts", - " | undefined; actionGroups: ", + " | undefined; producer: string; actionGroups: ", { "pluginId": "alerting", "scope": "common", @@ -1917,7 +1917,7 @@ "section": "def-common.ActionGroup", "text": "ActionGroup" }, - " | undefined; producer: string; actionVariables?: { context?: ", + " | undefined; actionVariables?: { context?: ", { "pluginId": "alerting", "scope": "common", @@ -2573,7 +2573,7 @@ "signature": [ "> & OutputOf>>>(request: TSearchRequest) => Promise<", + ", TAlertDoc = Partial> & OutputOf>>>(request: TSearchRequest) => Promise<", { "pluginId": "@kbn/es-types", "scope": "common", @@ -3210,7 +3210,7 @@ "label": "getAlertByAlertUuid", "description": [], "signature": [ - "(alertUuid: string) => Promise> & OutputOf>> | null> | null" + "(alertUuid: string) => Promise> & OutputOf>> | null> | null" ], "path": "x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts", "deprecated": false, @@ -4603,7 +4603,7 @@ "label": "parseTechnicalFields", "description": [], "signature": [ - "(input: unknown, partial?: boolean) => OutputOf>" + "(input: unknown, partial?: boolean) => OutputOf>" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, @@ -4979,7 +4979,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly 'event.action'?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" + "{ readonly \"@timestamp\": string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.name\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.uuid\": string; readonly \"kibana.space_ids\": string[]; readonly \"event.action\"?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.alert.flapping_history\"?: boolean[] | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"event.kind\"?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 01e03df7443c0..546f47c4b94aa 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-27 +date: 2023-03-01 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 d97cab5c93af0..300fb2cc0fa5e 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-27 +date: 2023-03-01 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 174da172a54e0..0e01a4ef5ae8f 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-27 +date: 2023-03-01 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.mdx b/api_docs/saved_objects_finder.mdx index 0ab98d7d02ba1..367ab10b93f95 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 526ab50e91670..ab25f08c08353 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-27 +date: 2023-03-01 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 505f2a42dc503..cf634e8bcf4d2 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-27 +date: 2023-03-01 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 1ef30026b02e6..58754d7347a9a 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-27 +date: 2023-03-01 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 d3e548f519309..d366a5659c2d0 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-27 +date: 2023-03-01 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 82924478cc916..f73392463bf85 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-27 +date: 2023-03-01 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 b7b7e30361372..5aeff28137085 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-27 +date: 2023-03-01 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 005133426198a..41a4f223df1c3 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 037917ccefd0a..f8cb3ecf363dd 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -95,7 +95,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly securityFlyoutEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 2b59497a340fd..888af88e4afd8 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-27 +date: 2023-03-01 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 02183745d657e..8e622aa8c0fbe 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-27 +date: 2023-03-01 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 9d2931bad5e60..280a9087fffdc 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-27 +date: 2023-03-01 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 39f5d7c1ae7b2..23bdf19710ba6 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-27 +date: 2023-03-01 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 a9bc92d8a42e0..1887661117d27 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-27 +date: 2023-03-01 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 be729d6bc2ecf..d6e565746caed 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-27 +date: 2023-03-01 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 5060499f75c43..74034e03d1606 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-27 +date: 2023-03-01 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 860ecb737b655..7fcd51007ee22 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.devdocs.json b/api_docs/telemetry.devdocs.json index ce31c34065ea5..cca1ac23391c3 100644 --- a/api_docs/telemetry.devdocs.json +++ b/api_docs/telemetry.devdocs.json @@ -119,7 +119,7 @@ "Should the telemetry payloads be sent from the server or the browser?" ], "signature": [ - "\"server\" | \"browser\"" + "\"browser\" | \"server\"" ], "path": "src/plugins/telemetry/public/plugin.ts", "deprecated": false, diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 8949000fbd29f..25f3f0790c8e5 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-27 +date: 2023-03-01 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 5e94c6b49e610..3b445eef5c9f8 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-27 +date: 2023-03-01 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 a19d535900d1f..494d687861aef 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-27 +date: 2023-03-01 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 1d97c165c128a..8c43e25170a8a 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-27 +date: 2023-03-01 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 7623f52a7f706..f10c81a6c4bf9 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-27 +date: 2023-03-01 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 47c6749027461..dffa97af77387 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-27 +date: 2023-03-01 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 f169e72c7236e..356e1f00b40fe 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index acac8c2932fb4..a5736cefd3749 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -2703,7 +2703,7 @@ "description": [], "signature": [ "BasicFields", - " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; } & { [x: string]: unknown[]; }" + " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; } & { [x: string]: unknown[]; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, @@ -4172,7 +4172,7 @@ "section": "def-common.RuleType", "text": "RuleType" }, - ", \"id\" | \"name\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"producer\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" + ", \"id\" | \"name\" | \"producer\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index cc5da08f265c9..25e1f7667b80f 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-27 +date: 2023-03-01 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 5f66671a8e6ee..af0e4ad1fe0e4 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-27 +date: 2023-03-01 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 7cc0b4f939a2c..237e122541d20 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-27 +date: 2023-03-01 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 0f60b4e9df3a7..53e998016795e 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-27 +date: 2023-03-01 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 a521051843765..f9421f8e37385 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-27 +date: 2023-03-01 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..d0f779daec6fb 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", @@ -1937,9 +1937,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 a9a3f8bb2ca79..e253a60f45f2f 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 22f36ce73aeea..7b878eafe283c 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 79040e0c7cdd2..823876429efb1 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-27 +date: 2023-03-01 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 284a2037ab90b..109cbf52dc582 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-27 +date: 2023-03-01 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 6fd75ea172182..638a8ea29d98e 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-27 +date: 2023-03-01 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 0be33385141aa..c31cbd33a8736 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-27 +date: 2023-03-01 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 2c3d8f2e3aeb1..f5b9dd978f640 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-27 +date: 2023-03-01 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 c9cbbb1faa746..dbc51a17cbeba 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-27 +date: 2023-03-01 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 aa93bc6b4f587..12355ae17b46c 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-27 +date: 2023-03-01 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 70d76d4c300bb..a84e017f11233 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-27 +date: 2023-03-01 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 0b2176aad94b4..87a277e1cd1db 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-27 +date: 2023-03-01 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 4312898bd792a..c1fc09b66945d 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-27 +date: 2023-03-01 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 5f30340988ad6..234f683885678 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-27 +date: 2023-03-01 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 9f1265bde99de..9584fabdc1fb9 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-27 +date: 2023-03-01 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 fae5f15be6a53..cfaf9011a3e5e 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-27 +date: 2023-03-01 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 ed906cba8ff0f..b7661a706bb18 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-27 +date: 2023-03-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; 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/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index e169bcfc24869..b7ca2f3c58b55 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -3,7 +3,7 @@ -- -Alerting allows you to define _rules_ to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with {observability-guide}/create-alerts.html[*Observability*], {security-guide}/prebuilt-rules.html[*Security*], <> and {ml-docs}/ml-configuring-alerts.html[*{ml-app}*], can be centrally managed from the <> UI, and provides a set of built-in <> and <> (known as stack rules) for you to use. +Alerting enables you to define _rules_, which detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with {observability-guide}/create-alerts.html[*{observability}*], {security-guide}/prebuilt-rules.html[*Security*], <> and {ml-docs}/ml-configuring-alerts.html[*{ml-app}*]. It can be centrally managed from *{stack-manage-app}* and provides a set of built-in <> and <> for you to use. image::images/alerting-overview.png[{rules-ui} UI] @@ -12,15 +12,12 @@ image::images/alerting-overview.png[{rules-ui} UI] To make sure you can access alerting and actions, see the <> section. ============================================== -[float] -== Concepts and terminology - Alerting works by running checks on a schedule to detect conditions defined by a rule. When a condition is met, the rule tracks it as an _alert_ and responds by triggering one or more _actions_. -Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ allow actions to talk to these services and integrations. +Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ enable actions to talk to these services and integrations. This section describes all of these elements and how they operate together. [float] -=== Rules +== Rules A rule specifies a background task that runs on the {kib} server to check for specific conditions. {kib} provides two types of rules: stack rules that are built into {kib} and the rules that are registered by {kib} apps. For more information, refer to <>. @@ -42,7 +39,7 @@ The following sections describe each part of the rule in more detail. [float] [[alerting-concepts-conditions]] -==== Conditions +=== Conditions Under the hood, {kib} rules detect conditions by running a JavaScript function on the {kib} server, which gives it the flexibility to support a wide range of conditions, anything from the results of a simple {es} query to heavy computations involving data from multiple sources or external systems. @@ -55,58 +52,47 @@ See <> for the rules provided by {kib} and how they express their co [float] [[alerting-concepts-scheduling]] -==== Schedule +=== Schedule Rule schedules are defined as an interval between subsequent checks, and can range from a few seconds to months. [IMPORTANT] ============================================== -The intervals of rule checks in {kib} are approximate. Their timing is affected by factors such as the frequency at which tasks are claimed and the task load on the system. Refer to <> for more information. +The intervals of rule checks in {kib} are approximate. Their timing is affected by factors such as the frequency at which tasks are claimed and the task load on the system. Refer to <> for more information. ============================================== [float] [[alerting-concepts-actions]] -==== Actions +=== Actions -Actions are invocations of connectors, which allow interaction with {kib} services or integrations with third-party systems. Actions run as background tasks on the {kib} server when rule conditions are met. +Actions run as background tasks on the {kib} server when rule conditions are met. Recovery actions likewise run when rule conditions are no longer met. They send notifications by connecting with services inside {kib} or integrating with third-party systems. When defining actions in a rule, you specify: -* The _connector type_: the type of service or integration to use -* The connection for that type by referencing a <> +* A connector +* An action frequency * A mapping of rule values to properties exposed for that type of action -The result is a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. +Rather than repeatedly entering connection information and credentials for each action, {kib} simplifies action setup using <>. For example if four rules send email notifications via the same SMTP service, they can all reference the same SMTP connector. -In the server monitoring example, the `email` connector type is used, and `server` is mapped to the body of the email, using the template string `CPU on {{server}} is high`. +The _action frequency_ defines when the action runs (for example, only when the alert status changes or at specific time intervals). Each rule type also has a set of the _action groups_ that affects when the action runs (for example, when the threshold is met or when the alert is recovered). If you want to reduce the number of notifications you receive without affecting their timeliness, some rule types support alert summaries. You can set the action frequency such that you receive notifications that summarize the new, ongoing, and recovered alerts at your preferred time intervals. -When the rule detects the condition, it creates an <> containing the details of the condition, renders the template with these details such as server name, and runs the action on the {kib} server by invoking the `email` connector type. +Each action definition is therefore a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. -image::images/what-is-an-action.svg[Actions are like templates that are rendered when an alert detects a condition] +In the server monitoring example, the `email` connector type is used, and `server` is mapped to the body of the email, using the template string `CPU on {{server}} is high`. -See <> for details on the types of connectors provided by {kib}. +When the rule detects the condition, it creates an alert containing the details of the condition. [float] [[alerting-concepts-alerts]] -=== Alerts +== Alerts -When checking for a condition, a rule might identify multiple occurrences of the condition. {kib} tracks each of these *alerts* separately and takes an action per alert. +When checking for a condition, a rule might identify multiple occurrences of the condition. {kib} tracks each of these alerts separately. Depending on the action frequency, an action occurs per alert or at the specified alert summary interval. -Using the server monitoring example, each server with average CPU > 0.9 is tracked as an alert. This means a separate email is sent for each server that exceeds the threshold. +Using the server monitoring example, each server with average CPU > 0.9 is tracked as an alert. This means a separate email is sent for each server that exceeds the threshold whenever the alert status changes. image::images/alerts.svg[{kib} tracks each detected condition as an alert and takes action on each alert] -[float] -[[alerting-concepts-connectors]] -=== Connectors - -Actions often involve connecting with services inside {kib} or integrating with third-party systems. -Rather than repeatedly entering connection information and credentials for each action, {kib} simplifies action setup using connectors. - -Connectors provide a central place to store connection information for services and integrations. For example if four rules send email notifications via the same SMTP service, they can all reference the same SMTP connector. When the SMTP settings change, you can update them once in the connector, instead of having to update four rules. - -image::images/rule-concepts-connectors.svg[Connectors provide a central place to store service connection settings] - [float] == Putting it all together @@ -114,10 +100,10 @@ A rule consists of conditions, actions, and a schedule. When conditions are met, image::images/rule-concepts-summary.svg[Rules, connectors, alerts and actions work together to convert detection into action] -. Anytime a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. -. Alerts create actions as long as they are not muted or throttled. When actions are created, the template that was setup in the rule is filled with actual values. In this example, three actions are created, and the template string {{server}} is replaced with the server name for each alert. -. {kib} invokes the actions, sending them to a third party integration like an email service. -. If the third party integration has connection parameters or credentials, {kib} will fetch these from the connector referenced in the action. +. Any time a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. +. Alerts create actions according to the action frequency, as long as they are not muted or throttled. When actions are created, its properties are filled with actual values. In this example, three actions are created when the threshold is met, and the template string {{server}} is replaced with the appropriate server name for each alert. +. {kib} runs the actions, sending notifications by using a third party integration like an email service. +. If the third party integration has connection parameters or credentials, {kib} fetches these from the appropriate connector. [float] [[alerting-concepts-differences]] @@ -135,7 +121,7 @@ Functionally, the {alert-features} differ in that: * Scheduled checks are run on {kib} instead of {es} * {kib} <> through rule types, whereas watches provide low-level control over inputs, conditions, and transformations. * {kib} rules track and persist the state of each detected condition through alerts. This makes it possible to mute and throttle individual alerts, and detect changes in state such as resolution. -* Actions are linked to alerts in Alerting. Actions are fired for each occurrence of a detected condition, rather than for the entire rule. +* Actions are linked to alerts. Actions are fired for each occurrence of a detected condition, rather than for the entire rule. At a higher level, the {alert-features} allow rich integrations across use cases like <>, <>, <>, and <>. Prepackaged rule types simplify setup and hide the details of complex, domain-specific detections, while providing a consistent interface across {kib}. diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index d381087809620..86716a99f51ab 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -79,21 +79,21 @@ Each connector enables different action properties. For example, an email connec [[alerting-concepts-suppressing-duplicate-notifications]] [TIP] ============================================== -If you are not using alert summaries, actions are triggered per alert and a rule can end up generating a large number of actions. Take the following example where a rule is monitoring three servers every minute for CPU usage > 0.9, and the rule is set to notify `On check intervals`: +If you are not using alert summaries, actions are triggered per alert and a rule can end up generating a large number of actions. Take the following example where a rule is monitoring three servers every minute for CPU usage > 0.9, and the action frequency is `On check intervals`: * Minute 1: server X123 > 0.9. _One email_ is sent for server X123. * Minute 2: X123 and Y456 > 0.9. _Two emails_ are sent, one for X123 and one for Y456. * Minute 3: X123, Y456, Z789 > 0.9. _Three emails_ are sent, one for each of X123, Y456, Z789. In this example, three emails are sent for server X123 in the span of 3 minutes for the same rule. Often, it's desirable to suppress these re-notifications. If -you set the rule notify setting to `On custom action intervals` with an interval of 5 minutes, you reduce noise by getting emails only every 5 minutes for +you set the action frequency to `On custom action intervals` with an interval of 5 minutes, you reduce noise by getting emails only every 5 minutes for servers that continue to exceed the threshold: -* Minute 1: server X123 > 0.9. _One email_ is sent for server X123. -* Minute 2: X123 and Y456 > 0.9. _One email_ is sent for Y456. -* Minute 3: X123, Y456, Z789 > 0.9. _One email_ is sent for Z789. +* Minute 1: server X123 > 0.9. _One email_ will be sent for server X123. +* Minute 2: X123 and Y456 > 0.9. _One email_ will be sent for Y456. +* Minute 3: X123, Y456, Z789 > 0.9. _One email_ will be sent for Z789. -To get notified only once when a server exceeds the threshold, you can set the rule notify setting to `On status changes`. +To get notified only once when a server exceeds the threshold, you can set the action frequency to `On status changes`. Alternatively, if the rule type supports alert summaries, consider using them to reduce the volume of notifications. ============================================== [float] diff --git a/docs/user/alerting/images/alerts.svg b/docs/user/alerting/images/alerts.svg index 022b3106ae802..5e7819dd583f1 100644 --- a/docs/user/alerting/images/alerts.svg +++ b/docs/user/alerting/images/alerts.svg @@ -1 +1,62 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/rule-concepts-connectors.svg b/docs/user/alerting/images/rule-concepts-connectors.svg deleted file mode 100644 index caee5f858fea9..0000000000000 --- a/docs/user/alerting/images/rule-concepts-connectors.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/user/alerting/images/rule-concepts-summary.svg b/docs/user/alerting/images/rule-concepts-summary.svg index aed7020b9d3e2..d7fd2c5806b39 100644 --- a/docs/user/alerting/images/rule-concepts-summary.svg +++ b/docs/user/alerting/images/rule-concepts-summary.svg @@ -1 +1,109 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/what-is-a-rule.svg b/docs/user/alerting/images/what-is-a-rule.svg index 2117e448ba136..2f89c2b4edd59 100644 --- a/docs/user/alerting/images/what-is-a-rule.svg +++ b/docs/user/alerting/images/what-is-a-rule.svg @@ -1 +1,24 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/what-is-an-action.svg b/docs/user/alerting/images/what-is-an-action.svg deleted file mode 100644 index f8435ee24fc19..0000000000000 --- a/docs/user/alerting/images/what-is-an-action.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/plugins/content_management/.storybook/main.js b/examples/content_management_examples/.storybook/main.js similarity index 100% rename from src/plugins/content_management/.storybook/main.js rename to examples/content_management_examples/.storybook/main.js diff --git a/examples/content_management_examples/README.md b/examples/content_management_examples/README.md new file mode 100644 index 0000000000000..a4b0486903dba --- /dev/null +++ b/examples/content_management_examples/README.md @@ -0,0 +1,3 @@ +# Content Management Examples + +An example plugin that shows how to integrate with the Kibana "content management" plugin. diff --git a/examples/content_management_examples/common/examples/todos/index.ts b/examples/content_management_examples/common/examples/todos/index.ts new file mode 100644 index 0000000000000..9ef986ec2032f --- /dev/null +++ b/examples/content_management_examples/common/examples/todos/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './todos'; diff --git a/examples/content_management_examples/common/examples/todos/todos.ts b/examples/content_management_examples/common/examples/todos/todos.ts new file mode 100644 index 0000000000000..0371651c128f9 --- /dev/null +++ b/examples/content_management_examples/common/examples/todos/todos.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 { schema } from '@kbn/config-schema'; +import { + CreateIn, + DeleteIn, + GetIn, + SearchIn, + UpdateIn, +} from '@kbn/content-management-plugin/common'; + +export const TODO_CONTENT_ID = 'todos'; +export interface Todo { + id: string; + title: string; + completed: boolean; +} +const todoSchema = schema.object({ + id: schema.string(), + title: schema.string(), + completed: schema.boolean(), +}); + +export type TodoCreateIn = CreateIn<'todos', { title: string }>; +export type TodoCreateOut = Todo; // TODO: Is this correct? +export const createInSchema = schema.object({ title: schema.string() }); +export const createOutSchema = todoSchema; + +export type TodoUpdateIn = UpdateIn<'todos', Partial>>; +export type TodoUpdateOut = Todo; +export const updateInSchema = schema.object({ + title: schema.maybe(schema.string()), + completed: schema.maybe(schema.boolean()), +}); +export const updateOutSchema = todoSchema; + +export type TodoDeleteIn = DeleteIn<'todos', { id: string }>; +export type TodoDeleteOut = void; + +export type TodoGetIn = GetIn<'todos'>; +export type TodoGetOut = Todo; +export const getOutSchema = todoSchema; + +export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>; +export interface TodoSearchOut { + hits: Todo[]; +} +export const searchInSchema = schema.object({ + filter: schema.maybe( + schema.oneOf([schema.literal('todo'), schema.literal('completed')], { + defaultValue: undefined, + }) + ), +}); +export const searchOutSchema = schema.object({ + hits: schema.arrayOf(todoSchema), +}); diff --git a/examples/content_management_examples/jest.config.js b/examples/content_management_examples/jest.config.js new file mode 100644 index 0000000000000..9f84c97053475 --- /dev/null +++ b/examples/content_management_examples/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/examples/content_management_examples'], +}; diff --git a/examples/content_management_examples/kibana.jsonc b/examples/content_management_examples/kibana.jsonc new file mode 100644 index 0000000000000..24759d7abdfb6 --- /dev/null +++ b/examples/content_management_examples/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/content-management-examples-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin integrating with content management plugin", + "plugin": { + "id": "contentManagementExamples", + "server": true, + "browser": true, + "requiredPlugins": [ + "contentManagement", + "developerExamples" + ] + } +} diff --git a/examples/content_management_examples/public/examples/index.tsx b/examples/content_management_examples/public/examples/index.tsx new file mode 100644 index 0000000000000..715f3a6809581 --- /dev/null +++ b/examples/content_management_examples/public/examples/index.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 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 ReactDOM from 'react-dom'; +import { EuiPageTemplate } from '@elastic/eui'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import { StartDeps } from '../types'; +import { TodoApp } from './todos'; + +export const renderApp = ( + { notifications }: CoreStart, + { contentManagement }: StartDeps, + { element }: AppMountParameters +) => { + ReactDOM.render( + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/content_management_examples/public/examples/todos/index.tsx b/examples/content_management_examples/public/examples/todos/index.tsx new file mode 100644 index 0000000000000..dd19feb3e0a80 --- /dev/null +++ b/examples/content_management_examples/public/examples/todos/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TodoApp } from './todo_app'; diff --git a/src/plugins/content_management/demo/todo/todo.stories.test.tsx b/examples/content_management_examples/public/examples/todos/stories/todo.stories.test.tsx similarity index 100% rename from src/plugins/content_management/demo/todo/todo.stories.test.tsx rename to examples/content_management_examples/public/examples/todos/stories/todo.stories.test.tsx diff --git a/src/plugins/content_management/demo/todo/todo.stories.tsx b/examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx similarity index 88% rename from src/plugins/content_management/demo/todo/todo.stories.tsx rename to examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx index 23cd2a194cf6d..5b7b8f1e23823 100644 --- a/src/plugins/content_management/demo/todo/todo.stories.tsx +++ b/examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx @@ -7,8 +7,9 @@ */ import * as React from 'react'; -import { Todos } from './todos'; -import { ContentClientProvider, ContentClient } from '../../public/content_client'; +import { ContentClientProvider, ContentClient } from '@kbn/content-management-plugin/public'; + +import { Todos } from '../todos'; import { TodosClient } from './todos_client'; export default { diff --git a/src/plugins/content_management/demo/todo/todos_client.ts b/examples/content_management_examples/public/examples/todos/stories/todos_client.ts similarity index 62% rename from src/plugins/content_management/demo/todo/todos_client.ts rename to examples/content_management_examples/public/examples/todos/stories/todos_client.ts index c6f5fe4bf5f36..04d1f6fb990ed 100644 --- a/src/plugins/content_management/demo/todo/todos_client.ts +++ b/examples/content_management_examples/public/examples/todos/stories/todos_client.ts @@ -7,28 +7,32 @@ */ import { v4 as uuidv4 } from 'uuid'; -import type { CrudClient } from '../../public/crud_client'; -import type { CreateIn, DeleteIn, GetIn, SearchIn, UpdateIn } from '../../common'; - -export interface Todo { - id: string; - title: string; - completed: boolean; -} - -export type TodoCreateIn = CreateIn<'todos', { title: string }>; -export type TodoUpdateIn = UpdateIn<'todos', Partial>>; -export type TodoDeleteIn = DeleteIn<'todos', { id: string }>; -export type TodoGetIn = GetIn<'todos'>; -export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>; +import type { CrudClient } from '@kbn/content-management-plugin/public'; +import type { + TodoCreateIn, + TodoUpdateIn, + TodoDeleteIn, + TodoGetIn, + TodoSearchIn, + TodoUpdateOut, + TodoCreateOut, + TodoSearchOut, + TodoDeleteOut, + Todo, + TodoGetOut, +} from '../../../../common/examples/todos'; +/** + * This client is used in the storybook examples to simulate a server-side registry client + * and to show how a content type can have a custom client-side CRUD client without using the server-side registry + */ export class TodosClient implements CrudClient { private todos: Todo[] = [ { id: uuidv4(), title: 'Learn Elasticsearch', completed: true }, { id: uuidv4(), title: 'Learn Kibana', completed: false }, ]; - async create(input: TodoCreateIn): Promise { + async create(input: TodoCreateIn): Promise { const todo = { id: uuidv4(), title: input.data.title, @@ -38,22 +42,22 @@ export class TodosClient implements CrudClient { return todo; } - async delete(input: TodoDeleteIn): Promise { + async delete(input: TodoDeleteIn): Promise { this.todos = this.todos.filter((todo) => todo.id !== input.id); } - async get(input: TodoGetIn): Promise { + async get(input: TodoGetIn): Promise { return this.todos.find((todo) => todo.id === input.id)!; } - async search(input: TodoSearchIn): Promise<{ hits: Todo[] }> { + async search(input: TodoSearchIn): Promise { const filter = input.query.filter; if (filter === 'todo') return { hits: this.todos.filter((t) => !t.completed) }; if (filter === 'completed') return { hits: this.todos.filter((t) => t.completed) }; return { hits: [...this.todos] }; } - async update(input: TodoUpdateIn): Promise { + async update(input: TodoUpdateIn): Promise { const idToUpdate = input.id; const todoToUpdate = this.todos.find((todo) => todo.id === idToUpdate)!; if (todoToUpdate) { diff --git a/examples/content_management_examples/public/examples/todos/todo_app.tsx b/examples/content_management_examples/public/examples/todos/todo_app.tsx new file mode 100644 index 0000000000000..d2fae22bb41a1 --- /dev/null +++ b/examples/content_management_examples/public/examples/todos/todo_app.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public'; +import { Todos } from './todos'; + +export const TodoApp = (props: { contentClient: ContentClient }) => { + return ( + + + + ); +}; diff --git a/src/plugins/content_management/demo/todo/todos.tsx b/examples/content_management_examples/public/examples/todos/todos.tsx similarity index 80% rename from src/plugins/content_management/demo/todo/todos.tsx rename to examples/content_management_examples/public/examples/todos/todos.tsx index 667e455029ab4..84c48aa24c88b 100644 --- a/src/plugins/content_management/demo/todo/todos.tsx +++ b/examples/content_management_examples/public/examples/todos/todos.tsx @@ -7,22 +7,32 @@ */ import React from 'react'; import { EuiButtonGroup, EuiButtonIcon, EuiCheckbox, EuiFieldText, EuiSpacer } from '@elastic/eui'; - import { useCreateContentMutation, useDeleteContentMutation, useSearchContentQuery, useUpdateContentMutation, - // eslint-disable-next-line @kbn/imports/no_boundary_crossing -} from '../../public/content_client'; -import type { Todo, TodoCreateIn, TodoDeleteIn, TodoSearchIn, TodoUpdateIn } from './todos_client'; +} from '@kbn/content-management-plugin/public'; + +import { + TODO_CONTENT_ID, + Todo, + TodoCreateIn, + TodoDeleteIn, + TodoSearchIn, + TodoUpdateIn, + TodoUpdateOut, + TodoCreateOut, + TodoSearchOut, + TodoDeleteOut, +} from '../../../common/examples/todos'; -const useCreateTodoMutation = () => useCreateContentMutation(); -const useDeleteTodoMutation = () => useDeleteContentMutation(); -const useUpdateTodoMutation = () => useUpdateContentMutation(); +const useCreateTodoMutation = () => useCreateContentMutation(); +const useDeleteTodoMutation = () => useDeleteContentMutation(); +const useUpdateTodoMutation = () => useUpdateContentMutation(); const useSearchTodosQuery = ({ filter }: { filter: TodoSearchIn['query']['filter'] }) => - useSearchContentQuery({ - contentTypeId: 'todos', + useSearchContentQuery({ + contentTypeId: TODO_CONTENT_ID, query: { filter }, }); @@ -70,14 +80,17 @@ export const Todos = () => {
      {data.hits.map((todo: Todo) => ( -
    • +
    • { updateTodoMutation.mutate({ - contentTypeId: 'todos', + contentTypeId: TODO_CONTENT_ID, id: todo.id, data: { completed: e.target.checked, @@ -85,7 +98,7 @@ export const Todos = () => { }); }} label={todo.title} - data-test-subj={`todoCheckbox-${todo.id}`} + data-test-subj={`todoCheckbox todoCheckbox-${todo.id}`} /> { aria-label="Delete" color="danger" onClick={() => { - deleteTodoMutation.mutate({ contentTypeId: 'todos', id: todo.id }); + deleteTodoMutation.mutate({ contentTypeId: TODO_CONTENT_ID, id: todo.id }); }} />
    • @@ -112,7 +125,7 @@ export const Todos = () => { if (!inputRef || !inputRef.value) return; createTodoMutation.mutate({ - contentTypeId: 'todos', + contentTypeId: TODO_CONTENT_ID, data: { title: inputRef.value, }, diff --git a/examples/content_management_examples/public/index.ts b/examples/content_management_examples/public/index.ts new file mode 100644 index 0000000000000..ba96df2040435 --- /dev/null +++ b/examples/content_management_examples/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ContentManagementExamplesPlugin } from './plugin'; + +export function plugin() { + return new ContentManagementExamplesPlugin(); +} diff --git a/examples/content_management_examples/public/plugin.ts b/examples/content_management_examples/public/plugin.ts new file mode 100644 index 0000000000000..8731ef667650a --- /dev/null +++ b/examples/content_management_examples/public/plugin.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { AppNavLinkStatus } from '@kbn/core-application-browser'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { StartDeps, SetupDeps } from './types'; + +export class ContentManagementExamplesPlugin + implements Plugin +{ + public setup(core: CoreSetup, { contentManagement, developerExamples }: SetupDeps) { + developerExamples.register({ + appId: `contentManagementExamples`, + title: `Content Management Examples`, + description: 'Example plugin for the content management plugin', + }); + + core.application.register({ + id: `contentManagementExamples`, + title: `Content Management Examples`, + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + const { renderApp } = await import('./examples'); + const [coreStart, deps] = await core.getStartServices(); + return renderApp(coreStart, deps, params); + }, + }); + + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/examples/content_management_examples/public/types.ts b/examples/content_management_examples/public/types.ts new file mode 100644 index 0000000000000..83e65c5455afd --- /dev/null +++ b/examples/content_management_examples/public/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + ContentManagementPublicSetup, + ContentManagementPublicStart, +} from '@kbn/content-management-plugin/public'; +import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; + +export interface SetupDeps { + contentManagement: ContentManagementPublicSetup; + developerExamples: DeveloperExamplesSetup; +} + +export interface StartDeps { + contentManagement: ContentManagementPublicStart; +} diff --git a/examples/content_management_examples/server/examples/todos/index.ts b/examples/content_management_examples/server/examples/todos/index.ts new file mode 100644 index 0000000000000..bf065ba480736 --- /dev/null +++ b/examples/content_management_examples/server/examples/todos/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { registerTodoContentType } from './todos'; diff --git a/examples/content_management_examples/server/examples/todos/todos.ts b/examples/content_management_examples/server/examples/todos/todos.ts new file mode 100644 index 0000000000000..fe8a7826be655 --- /dev/null +++ b/examples/content_management_examples/server/examples/todos/todos.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 { + ContentStorage, + StorageContext, + ContentManagementServerSetup, +} from '@kbn/content-management-plugin/server'; +import { v4 } from 'uuid'; +import { + createInSchema, + searchInSchema, + Todo, + TODO_CONTENT_ID, + updateInSchema, + TodoSearchOut, + TodoCreateOut, + TodoUpdateOut, + TodoDeleteOut, + TodoGetOut, + createOutSchema, + getOutSchema, + updateOutSchema, + searchOutSchema, + TodoUpdateIn, + TodoSearchIn, + TodoCreateIn, +} from '../../../common/examples/todos'; + +export const registerTodoContentType = ({ + contentManagement, +}: { + contentManagement: ContentManagementServerSetup; +}) => { + contentManagement.register({ + id: TODO_CONTENT_ID, + schemas: { + content: { + create: { + in: { + data: createInSchema, + }, + out: { + result: createOutSchema, + }, + }, + update: { + in: { + data: updateInSchema, + }, + out: { + result: updateOutSchema, + }, + }, + search: { + in: { + query: searchInSchema, + }, + out: { + result: searchOutSchema, + }, + }, + get: { + out: { + result: getOutSchema, + }, + }, + }, + }, + storage: new TodosStorage(), + }); +}; + +class TodosStorage implements ContentStorage { + private db: Map = new Map(); + + constructor() { + const id1 = v4(); + this.db.set(id1, { + id: id1, + title: 'Learn Elasticsearch', + completed: true, + }); + const id2 = v4(); + this.db.set(id2, { + id: id2, + title: 'Learn Kibana', + completed: false, + }); + } + + async get(ctx: StorageContext, id: string): Promise { + return this.db.get(id)!; + } + + async bulkGet(ctx: StorageContext, ids: string[]): Promise { + return ids.map((id) => this.db.get(id)!); + } + + async create(ctx: StorageContext, data: TodoCreateIn['data']): Promise { + const todo: Todo = { + ...data, + completed: false, + id: v4(), + }; + + this.db.set(todo.id, todo); + + return todo; + } + + async update( + ctx: StorageContext, + id: string, + data: TodoUpdateIn['data'] + ): Promise { + const content = this.db.get(id); + if (!content) { + throw new Error(`Content to update not found [${id}].`); + } + + const updatedContent = { + ...content, + ...data, + }; + + this.db.set(id, updatedContent); + + return updatedContent; + } + + async delete(ctx: StorageContext, id: string): Promise { + this.db.delete(id); + } + + async search(ctx: StorageContext, query: TodoSearchIn['query']): Promise { + const hits = Array.from(this.db.values()); + if (query.filter === 'todo') return { hits: hits.filter((t) => !t.completed) }; + if (query.filter === 'completed') return { hits: hits.filter((t) => t.completed) }; + return { hits }; + } +} diff --git a/examples/content_management_examples/server/index.ts b/examples/content_management_examples/server/index.ts new file mode 100644 index 0000000000000..d75af7bacf224 --- /dev/null +++ b/examples/content_management_examples/server/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginInitializerContext } from '@kbn/core/server'; +import { ContentManagementExamplesPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ContentManagementExamplesPlugin(initializerContext); +} diff --git a/examples/content_management_examples/server/plugin.ts b/examples/content_management_examples/server/plugin.ts new file mode 100644 index 0000000000000..bf66de46e1251 --- /dev/null +++ b/examples/content_management_examples/server/plugin.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 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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { SetupDeps, StartDeps } from './types'; +import { registerTodoContentType } from './examples/todos'; + +export class ContentManagementExamplesPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { contentManagement }: SetupDeps) { + registerTodoContentType({ contentManagement }); + return {}; + } + + public start(core: CoreStart, { contentManagement }: StartDeps) { + return {}; + } + + public stop() {} +} diff --git a/examples/content_management_examples/server/types.ts b/examples/content_management_examples/server/types.ts new file mode 100644 index 0000000000000..57427001461b0 --- /dev/null +++ b/examples/content_management_examples/server/types.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 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 { + ContentManagementServerSetup, + ContentManagementServerStart, +} from '@kbn/content-management-plugin/server'; + +export interface SetupDeps { + contentManagement: ContentManagementServerSetup; +} + +export interface StartDeps { + contentManagement: ContentManagementServerStart; +} diff --git a/examples/content_management_examples/tsconfig.json b/examples/content_management_examples/tsconfig.json new file mode 100644 index 0000000000000..1e899345b0bf4 --- /dev/null +++ b/examples/content_management_examples/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "common/**/*", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/developer-examples-plugin", + "@kbn/config-schema", + "@kbn/content-management-plugin", + "@kbn/core-application-browser", + ] +} diff --git a/package.json b/package.json index 548df53e3a629..70a10638029f7 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ }, "dependencies": { "@appland/sql-parser": "^1.5.1", - "@babel/runtime": "^7.20.13", + "@babel/runtime": "^7.21.0", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", @@ -96,7 +96,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.6.0-canary.3", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "75.1.0", + "@elastic/eui": "75.1.2", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -134,6 +134,7 @@ "@kbn/alerting-fixture-plugin": "link:x-pack/test/functional_with_es_ssl/plugins/alerts", "@kbn/alerting-plugin": "link:x-pack/plugins/alerting", "@kbn/alerts": "link:packages/kbn-alerts", + "@kbn/alerts-as-data-utils": "link:packages/kbn-alerts-as-data-utils", "@kbn/alerts-restricted-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted", "@kbn/alerts-ui-shared": "link:packages/kbn-alerts-ui-shared", "@kbn/analytics": "link:packages/kbn-analytics", @@ -181,6 +182,7 @@ "@kbn/config-schema": "link:packages/kbn-config-schema", "@kbn/console-plugin": "link:src/plugins/console", "@kbn/content-management-content-editor": "link:packages/content-management/content_editor", + "@kbn/content-management-examples-plugin": "link:examples/content_management_examples", "@kbn/content-management-plugin": "link:src/plugins/content_management", "@kbn/content-management-table-list": "link:packages/content-management/table_list", "@kbn/controls-example-plugin": "link:examples/controls_example", @@ -370,6 +372,7 @@ "@kbn/event-annotation-plugin": "link:src/plugins/event_annotation", "@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log", "@kbn/event-log-plugin": "link:x-pack/plugins/event_log", + "@kbn/expandable-flyout": "link:packages/kbn-expandable-flyout", "@kbn/exploratory-view-example-plugin": "link:x-pack/examples/exploratory_view_example", "@kbn/expression-error-plugin": "link:src/plugins/expression_error", "@kbn/expression-gauge-plugin": "link:src/plugins/chart_expressions/expression_gauge", @@ -934,26 +937,26 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@babel/cli": "^7.20.7", - "@babel/core": "^7.20.12", + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.20.14", + "@babel/generator": "^7.21.1", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/parser": "^7.20.15", + "@babel/parser": "^7.21.1", "@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", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", - "@babel/plugin-proposal-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-transform-runtime": "^7.19.6", + "@babel/plugin-transform-runtime": "^7.21.0", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.18.9", - "@babel/traverse": "^7.20.13", - "@babel/types": "^7.20.7", + "@babel/preset-typescript": "^7.21.0", + "@babel/register": "^7.21.0", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -1431,7 +1434,7 @@ "resolve": "^1.22.0", "rxjs-marbles": "^7.0.1", "sass-loader": "^10.4.1", - "selenium-webdriver": "^4.8.0", + "selenium-webdriver": "^4.8.1", "simple-git": "^3.16.0", "sinon": "^7.4.2", "sort-package-json": "^1.53.1", @@ -1446,7 +1449,7 @@ "svgo": "^2.8.0", "tape": "^5.0.1", "tempy": "^0.3.0", - "terser": "^5.16.3", + "terser": "^5.16.4", "terser-webpack-plugin": "^4.2.3", "tough-cookie": "^4.1.2", "tree-kill": "^1.2.2", diff --git a/packages/core/node/core-node-server-internal/src/node_config.test.ts b/packages/core/node/core-node-server-internal/src/node_config.test.ts new file mode 100644 index 0000000000000..cb5a1fb44cebd --- /dev/null +++ b/packages/core/node/core-node-server-internal/src/node_config.test.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 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 { rolesConfig } from './node_config'; + +describe('rolesConfig', () => { + test('default', () => { + expect(rolesConfig.validate(undefined)).toEqual(['*']); + }); + test('empty', () => { + expect(() => rolesConfig.validate([])).toThrow(); + }); + test('"ui" and "background_tasks" roles are allowed and can be combined', () => { + expect(() => rolesConfig.validate(['ui', 'background_tasks'])).not.toThrow(); + expect(() => rolesConfig.validate(['ui'])).not.toThrow(); + expect(() => rolesConfig.validate(['background_tasks'])).not.toThrow(); + }); + test('exlcusive "*"', () => { + const wildcardError = `wildcard ("*") cannot be used with other roles or specified more than once`; + expect(() => rolesConfig.validate(['*'])).not.toThrow(); + + expect(() => rolesConfig.validate(['*', 'ui'])).toThrow(wildcardError); + expect(() => rolesConfig.validate(['*', '*'])).toThrow(wildcardError); + + expect(() => rolesConfig.validate(['*', 'unknown'])).toThrow(); + }); + test('exlcusive "migrator"', () => { + const migratorError = `"migrator" cannot be used with other roles or specified more than once`; + expect(() => rolesConfig.validate(['migrator'])).not.toThrow(); + + expect(() => rolesConfig.validate(['migrator', 'ui'])).toThrow(migratorError); + expect(() => rolesConfig.validate(['migrator', 'migrator'])).toThrow(migratorError); + + expect(() => rolesConfig.validate(['migrator', 'unknown'])).toThrow(); + }); +}); diff --git a/packages/core/node/core-node-server-internal/src/node_config.ts b/packages/core/node/core-node-server-internal/src/node_config.ts index e49d8a589296c..f6e8afa694fb7 100644 --- a/packages/core/node/core-node-server-internal/src/node_config.ts +++ b/packages/core/node/core-node-server-internal/src/node_config.ts @@ -6,33 +6,68 @@ * Side Public License, v 1. */ -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; /** @internal */ export const NODE_CONFIG_PATH = 'node' as const; +/** + * Wildchar is a special config option that implies all {@link NODE_DEFAULT_ROLES} roles. + * @internal + */ +export const NODE_WILDCARD_CHAR = '*' as const; +/** @internal */ +export const NODE_BACKGROUND_TASKS_ROLE = 'background_tasks' as const; +/** @internal */ +export const NODE_UI_ROLE = 'ui' as const; /** @internal */ -export const NODE_WILDCARD_CHAR = '*'; +export const NODE_MIGRATOR_ROLE = 'migrator' as const; /** @internal */ -export const NODE_ACCEPTED_ROLES = ['background_tasks', 'ui']; +export const NODE_DEFAULT_ROLES = [NODE_BACKGROUND_TASKS_ROLE, NODE_UI_ROLE] as const; +/** @internal */ +export const NODE_ALL_ROLES = [ + NODE_UI_ROLE, + NODE_MIGRATOR_ROLE, + NODE_BACKGROUND_TASKS_ROLE, +] as const; + +/** @internal */ +export const rolesConfig = schema.arrayOf( + schema.oneOf([ + schema.literal(NODE_BACKGROUND_TASKS_ROLE), + schema.literal(NODE_MIGRATOR_ROLE), + schema.literal(NODE_WILDCARD_CHAR), + schema.literal(NODE_UI_ROLE), + ]), + { + defaultValue: [NODE_WILDCARD_CHAR], + validate: (value) => { + if (value.length > 1) { + if (value.includes(NODE_WILDCARD_CHAR)) { + return `wildcard ("*") cannot be used with other roles or specified more than once`; + } + if (value.includes(NODE_MIGRATOR_ROLE)) { + return `"migrator" cannot be used with other roles or specified more than once`; + } + } + }, + minSize: 1, + } +); + +/** @internal */ +export type NodeRolesConfig = TypeOf; /** @internal */ export interface NodeConfigType { - roles: string[]; + roles: NodeRolesConfig; } const configSchema = schema.object({ - roles: schema.oneOf( - [ - schema.arrayOf(schema.oneOf([schema.literal('background_tasks'), schema.literal('ui')])), - schema.arrayOf(schema.literal(NODE_WILDCARD_CHAR), { minSize: 1, maxSize: 1 }), - ], - { - defaultValue: [NODE_WILDCARD_CHAR], - } - ), + roles: rolesConfig, }); +/** @internal */ export const nodeConfig: ServiceConfigDescriptor = { path: NODE_CONFIG_PATH, schema: configSchema, diff --git a/packages/core/node/core-node-server-internal/src/node_service.test.ts b/packages/core/node/core-node-server-internal/src/node_service.test.ts index f25737ded31bb..464e3fbae79a7 100644 --- a/packages/core/node/core-node-server-internal/src/node_service.test.ts +++ b/packages/core/node/core-node-server-internal/src/node_service.test.ts @@ -11,12 +11,13 @@ import { BehaviorSubject } from 'rxjs'; import type { CoreContext } from '@kbn/core-base-server-internal'; import { NodeService } from './node_service'; +import type { NodeRolesConfig } from './node_config'; import { configServiceMock } from '@kbn/config-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; -const getMockedConfigService = (nodeConfig: unknown) => { +const getMockedConfigService = (nodeConfig: { roles: NodeRolesConfig }) => { const configService = configServiceMock.create(); configService.atPath.mockImplementation((path) => { if (path === 'node') { @@ -51,6 +52,7 @@ describe('NodeService', () => { expect(roles.backgroundTasks).toBe(true); expect(roles.ui).toBe(true); + expect(roles.migrator).toBe(false); }); it('returns correct roles when node is configured to `background_tasks`', async () => { @@ -62,6 +64,7 @@ describe('NodeService', () => { expect(roles.backgroundTasks).toBe(true); expect(roles.ui).toBe(false); + expect(roles.migrator).toBe(false); }); it('returns correct roles when node is configured to `ui`', async () => { @@ -73,6 +76,7 @@ describe('NodeService', () => { expect(roles.backgroundTasks).toBe(false); expect(roles.ui).toBe(true); + expect(roles.migrator).toBe(false); }); it('returns correct roles when node is configured to both `background_tasks` and `ui`', async () => { @@ -84,6 +88,19 @@ describe('NodeService', () => { expect(roles.backgroundTasks).toBe(true); expect(roles.ui).toBe(true); + expect(roles.migrator).toBe(false); + }); + + it('returns correct roles when node is configured to `migrator`', async () => { + configService = getMockedConfigService({ roles: ['migrator'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + const { roles } = await service.preboot({ loggingSystem: logger }); + + expect(roles.backgroundTasks).toBe(false); + expect(roles.ui).toBe(false); + expect(roles.migrator).toBe(true); }); it('logs the node roles', async () => { diff --git a/packages/core/node/core-node-server-internal/src/node_service.ts b/packages/core/node/core-node-server-internal/src/node_service.ts index b5c5c0a8b4c17..d84582d57e892 100644 --- a/packages/core/node/core-node-server-internal/src/node_service.ts +++ b/packages/core/node/core-node-server-internal/src/node_service.ts @@ -14,13 +14,15 @@ import type { ILoggingSystem } from '@kbn/core-logging-server-internal'; import type { NodeRoles } from '@kbn/core-node-server'; import type { Logger } from '@kbn/logging'; import { - NodeConfigType, - NODE_WILDCARD_CHAR, - NODE_ACCEPTED_ROLES, + type NodeConfigType, + type NodeRolesConfig, + NODE_ALL_ROLES, NODE_CONFIG_PATH, + NODE_WILDCARD_CHAR, + NODE_DEFAULT_ROLES, } from './node_config'; -const DEFAULT_ROLES = NODE_ACCEPTED_ROLES; +const DEFAULT_ROLES = [...NODE_DEFAULT_ROLES]; const containsWildcard = (roles: string[]) => roles.includes(NODE_WILDCARD_CHAR); /** @@ -66,8 +68,9 @@ export class NodeService { loggingSystem.setGlobalContext({ service: { node: { roles } } }); this.log.info(`Kibana process configured with roles: [${roles.join(', ')}]`); - this.roles = NODE_ACCEPTED_ROLES.reduce((acc, curr) => { - return { ...acc, [camelCase(curr)]: roles.includes(curr) }; + // We assume the combination of node roles has been validated and avoid doing additional checks here. + this.roles = NODE_ALL_ROLES.reduce((acc, curr) => { + return { ...acc, [camelCase(curr)]: (roles as string[]).includes(curr) }; }, {} as NodeRoles); return { @@ -86,7 +89,7 @@ export class NodeService { // nothing to do here yet } - private async getNodeRoles(): Promise { + private async getNodeRoles(): Promise { const { roles } = await firstValueFrom( this.configService.atPath(NODE_CONFIG_PATH) ); diff --git a/packages/core/node/core-node-server-mocks/src/node_service.mock.ts b/packages/core/node/core-node-server-mocks/src/node_service.mock.ts index ff354b663c231..2ab7f304e943a 100644 --- a/packages/core/node/core-node-server-mocks/src/node_service.mock.ts +++ b/packages/core/node/core-node-server-mocks/src/node_service.mock.ts @@ -18,6 +18,7 @@ const createInternalPrebootContractMock = () => { roles: { backgroundTasks: true, ui: true, + migrator: false, }, }; return prebootContract; @@ -27,15 +28,18 @@ const createInternalStartContractMock = ( { ui, backgroundTasks, + migrator, }: { ui: boolean; backgroundTasks: boolean; - } = { ui: true, backgroundTasks: true } + migrator: boolean; + } = { ui: true, backgroundTasks: true, migrator: false } ) => { const startContract: jest.Mocked = { roles: { backgroundTasks, ui, + migrator, }, }; return startContract; diff --git a/packages/core/node/core-node-server/src/types.ts b/packages/core/node/core-node-server/src/types.ts index 9b5a889b2ae6d..f2e594067b5ca 100644 --- a/packages/core/node/core-node-server/src/types.ts +++ b/packages/core/node/core-node-server/src/types.ts @@ -37,4 +37,9 @@ export interface NodeRoles { * to handle http traffic from the browser. */ ui: boolean; + /** + * Start Kibana with the specific purpose of completing the migrations phase then shutting down. + * @remark This role is special as it precludes the use of other roles. + */ + migrator: boolean; } diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/error_toast.tsx b/packages/core/notifications/core-notifications-browser-internal/src/toasts/error_toast.tsx index fb8b9305d817c..b1c21064d3c91 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/error_toast.tsx +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/error_toast.tsx @@ -74,7 +74,7 @@ function showErrorDialog({ {title} - + {text && ( diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/toasts_api.tsx b/packages/core/notifications/core-notifications-browser-internal/src/toasts/toasts_api.tsx index 43e95b68ade20..2b2de9f945c74 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/toasts_api.tsx +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/toasts_api.tsx @@ -150,7 +150,7 @@ export class ToastsApi implements IToasts { public addDanger(toastOrTitle: ToastInput, options?: ToastOptions) { return this.add({ color: 'danger', - iconType: 'alert', + iconType: 'error', toastLifeTimeMs: this.uiSettings.get('notifications:lifetime:warning'), ...normalizeToast(toastOrTitle), ...options, @@ -168,7 +168,7 @@ export class ToastsApi implements IToasts { const message = options.toastMessage || error.message; return this.add({ color: 'danger', - iconType: 'alert', + iconType: 'error', toastLifeTimeMs: this.uiSettings.get('notifications:lifetime:error'), text: mountReactNode( { roles: { backgroundTasks: true, ui: true, + migrator: false, }, }; diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts index bba736aa3d31f..8d2c723aa5cb8 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts @@ -195,7 +195,7 @@ describe('createPluginInitializerContext', () => { opaqueId, manifest: createPluginManifest(), instanceInfo, - nodeInfo: { roles: { backgroundTasks: false, ui: true } }, + nodeInfo: { roles: { backgroundTasks: false, ui: true, migrator: false } }, }); expect(pluginInitializerContext.node.roles.backgroundTasks).toBe(false); expect(pluginInitializerContext.node.roles.ui).toBe(true); diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index 62d2f2de6383e..595473e35ea59 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -78,6 +78,7 @@ export function createPluginInitializerContext({ roles: { backgroundTasks: nodeInfo.roles.backgroundTasks, ui: nodeInfo.roles.ui, + migrator: nodeInfo.roles.migrator, }, }, diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts index 62742499471cc..b8f8b5d2b9be5 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts @@ -728,7 +728,7 @@ describe('PluginsService', () => { }, coreContext: { coreId, env, logger, configService }, instanceInfo: { uuid: 'uuid' }, - nodeInfo: { roles: { backgroundTasks: true, ui: true } }, + nodeInfo: { roles: { backgroundTasks: true, ui: true, migrator: false } }, }); const logs = loggingSystemMock.collect(logger); diff --git a/packages/core/root/core-root-server-internal/index.ts b/packages/core/root/core-root-server-internal/index.ts index d6150b7aae8fc..5c1de0015e861 100644 --- a/packages/core/root/core-root-server-internal/index.ts +++ b/packages/core/root/core-root-server-internal/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { Server, Root, bootstrap } from './src'; +export { Server, registerServiceConfig, Root, bootstrap } from './src'; diff --git a/packages/core/root/core-root-server-internal/src/index.ts b/packages/core/root/core-root-server-internal/src/index.ts index 7573b34a28a6f..4d23b995211e6 100644 --- a/packages/core/root/core-root-server-internal/src/index.ts +++ b/packages/core/root/core-root-server-internal/src/index.ts @@ -7,5 +7,6 @@ */ export { Server } from './server'; +export { registerServiceConfig } from './register_service_config'; export { bootstrap } from './bootstrap'; export { Root } from './root'; diff --git a/packages/core/root/core-root-server-internal/src/register_service_config.ts b/packages/core/root/core-root-server-internal/src/register_service_config.ts new file mode 100644 index 0000000000000..a22ea56f25ee9 --- /dev/null +++ b/packages/core/root/core-root-server-internal/src/register_service_config.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 { config as pathConfig } from '@kbn/utils'; +import { ConfigService } from '@kbn/config'; +import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; +import { config as loggingConfig } from '@kbn/core-logging-server-internal'; +import { coreDeprecationProvider } from '@kbn/core-config-server-internal'; +import { nodeConfig } from '@kbn/core-node-server-internal'; +import { pidConfig } from '@kbn/core-environment-server-internal'; +import { executionContextConfig } from '@kbn/core-execution-context-server-internal'; +import { config as httpConfig, cspConfig, externalUrlConfig } from '@kbn/core-http-server-internal'; +import { config as elasticsearchConfig } from '@kbn/core-elasticsearch-server-internal'; +import { opsConfig } from '@kbn/core-metrics-server-internal'; +import { + savedObjectsConfig, + savedObjectsMigrationConfig, +} from '@kbn/core-saved-objects-base-server-internal'; +import { config as i18nConfig } from '@kbn/core-i18n-server-internal'; +import { config as deprecationConfig } from '@kbn/core-deprecations-server-internal'; +import { statusConfig } from '@kbn/core-status-server-internal'; +import { uiSettingsConfig } from '@kbn/core-ui-settings-server-internal'; + +import { config as pluginsConfig } from '@kbn/core-plugins-server-internal'; +import { elasticApmConfig } from './root/elastic_config'; + +const rootConfigPath = ''; + +export function registerServiceConfig(configService: ConfigService) { + const configDescriptors: Array> = [ + cspConfig, + deprecationConfig, + elasticsearchConfig, + elasticApmConfig, + executionContextConfig, + externalUrlConfig, + httpConfig, + i18nConfig, + loggingConfig, + nodeConfig, + opsConfig, + pathConfig, + pidConfig, + pluginsConfig, + savedObjectsConfig, + savedObjectsMigrationConfig, + statusConfig, + uiSettingsConfig, + ]; + + configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider); + for (const descriptor of configDescriptors) { + if (descriptor.deprecations) { + configService.addDeprecationProvider(descriptor.path, descriptor.deprecations); + } + configService.setSchema(descriptor.path, descriptor.schema); + } +} diff --git a/packages/core/root/core-root-server-internal/src/server.ts b/packages/core/root/core-root-server-internal/src/server.ts index d7580f19526d8..8c8d636d795e9 100644 --- a/packages/core/root/core-root-server-internal/src/server.ts +++ b/packages/core/root/core-root-server-internal/src/server.ts @@ -7,57 +7,30 @@ */ import apm from 'elastic-apm-node'; -import { config as pathConfig } from '@kbn/utils'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import type { Logger, LoggerFactory } from '@kbn/logging'; import { ConfigService, Env, RawConfigurationProvider } from '@kbn/config'; -import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; import { DocLinksService } from '@kbn/core-doc-links-server-internal'; -import { - LoggingService, - ILoggingSystem, - config as loggingConfig, -} from '@kbn/core-logging-server-internal'; -import { - coreDeprecationProvider, - ensureValidConfiguration, -} from '@kbn/core-config-server-internal'; -import { NodeService, nodeConfig } from '@kbn/core-node-server-internal'; +import { LoggingService, ILoggingSystem } from '@kbn/core-logging-server-internal'; +import { ensureValidConfiguration } from '@kbn/core-config-server-internal'; +import { NodeService } from '@kbn/core-node-server-internal'; import { AnalyticsService } from '@kbn/core-analytics-server-internal'; import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-server'; -import { EnvironmentService, pidConfig } from '@kbn/core-environment-server-internal'; -import { - ExecutionContextService, - executionContextConfig, -} from '@kbn/core-execution-context-server-internal'; +import { EnvironmentService } from '@kbn/core-environment-server-internal'; +import { ExecutionContextService } from '@kbn/core-execution-context-server-internal'; import { PrebootService } from '@kbn/core-preboot-server-internal'; import { ContextService } from '@kbn/core-http-context-server-internal'; -import { - HttpService, - config as httpConfig, - cspConfig, - externalUrlConfig, -} from '@kbn/core-http-server-internal'; -import { - ElasticsearchService, - config as elasticsearchConfig, -} from '@kbn/core-elasticsearch-server-internal'; -import { MetricsService, opsConfig } from '@kbn/core-metrics-server-internal'; +import { HttpService } from '@kbn/core-http-server-internal'; +import { ElasticsearchService } from '@kbn/core-elasticsearch-server-internal'; +import { MetricsService } from '@kbn/core-metrics-server-internal'; import { CapabilitiesService } from '@kbn/core-capabilities-server-internal'; import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; -import { - savedObjectsConfig, - savedObjectsMigrationConfig, -} from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsService } from '@kbn/core-saved-objects-server-internal'; -import { I18nService, config as i18nConfig } from '@kbn/core-i18n-server-internal'; -import { - DeprecationsService, - config as deprecationConfig, -} from '@kbn/core-deprecations-server-internal'; +import { I18nService } from '@kbn/core-i18n-server-internal'; +import { DeprecationsService } from '@kbn/core-deprecations-server-internal'; import { CoreUsageDataService } from '@kbn/core-usage-data-server-internal'; -import { StatusService, statusConfig } from '@kbn/core-status-server-internal'; -import { UiSettingsService, uiSettingsConfig } from '@kbn/core-ui-settings-server-internal'; +import { StatusService } from '@kbn/core-status-server-internal'; +import { UiSettingsService } from '@kbn/core-ui-settings-server-internal'; import { CustomBrandingService } from '@kbn/core-custom-branding-server-internal'; import { CoreRouteHandlerContext, @@ -75,16 +48,11 @@ import type { InternalCoreSetup, InternalCoreStart, } from '@kbn/core-lifecycle-server-internal'; -import { - DiscoveredPlugins, - PluginsService, - config as pluginsConfig, -} from '@kbn/core-plugins-server-internal'; +import { DiscoveredPlugins, PluginsService } from '@kbn/core-plugins-server-internal'; import { CoreAppsService } from '@kbn/core-apps-server-internal'; -import { elasticApmConfig } from './root/elastic_config'; +import { registerServiceConfig } from './register_service_config'; const coreId = Symbol('core'); -const rootConfigPath = ''; const KIBANA_STARTED_EVENT = 'kibana_started'; /** @internal */ @@ -465,34 +433,7 @@ export class Server { } public setupCoreConfig() { - const configDescriptors: Array> = [ - cspConfig, - deprecationConfig, - elasticsearchConfig, - elasticApmConfig, - executionContextConfig, - externalUrlConfig, - httpConfig, - i18nConfig, - loggingConfig, - nodeConfig, - opsConfig, - pathConfig, - pidConfig, - pluginsConfig, - savedObjectsConfig, - savedObjectsMigrationConfig, - statusConfig, - uiSettingsConfig, - ]; - - this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider); - for (const descriptor of configDescriptors) { - if (descriptor.deprecations) { - this.configService.addDeprecationProvider(descriptor.path, descriptor.deprecations); - } - this.configService.setSchema(descriptor.path, descriptor.schema); - } + registerServiceConfig(this.configService); } /** diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts index 22980bd8e88e7..c4d48d66a6a06 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts @@ -11,6 +11,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; const migrationSchema = schema.object({ + algorithm: schema.oneOf([schema.literal('v2'), schema.literal('zdt')], { + defaultValue: 'v2', + }), batchSize: schema.number({ defaultValue: 1_000 }), maxBatchSizeBytes: schema.byteSize({ defaultValue: '100mb' }), // 100mb is the default http.max_content_length Elasticsearch config value discardUnknownObjects: schema.maybe( 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 21fbd6f8b5329..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 @@ -6,10 +6,14 @@ * Side Public License, v 1. */ -export { DocumentMigrator, KibanaMigrator, buildActiveMappings, mergeTypes } from './src'; +export { DocumentMigrator, KibanaMigrator, buildActiveMappings, buildTypesMappings } from './src'; export type { KibanaMigratorOptions } from './src'; export { getAggregatedTypesDocuments } from './src/actions/check_for_unknown_docs'; -export { addExcludedTypesToBoolQuery } from './src/model/helpers'; +export { + addExcludedTypesToBoolQuery, + createBulkIndexOperationTuple, + createBulkDeleteOperationBody, +} from './src/model/helpers'; // these are only used for integration tests export { @@ -24,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/README.md b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md index 8655c3e9c2222..6ae7f7fb09c06 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md @@ -369,9 +369,9 @@ completed this step: - temp index has a write block - temp index is not found ### New control state -1. If `currentBatch` is the last batch in `transformedDocBatches` +1. If `currentBatch` is the last batch in `bulkOperationBatches` → `REINDEX_SOURCE_TO_TEMP_READ` -2. If there are more batches left in `transformedDocBatches` +2. If there are more batches left in `bulkOperationBatches` → `REINDEX_SOURCE_TO_TEMP_INDEX_BULK` ## REINDEX_SOURCE_TO_TEMP_CLOSE_PIT diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap index 6973b0b8a7081..cd2b218d58dd4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -18,6 +18,9 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [], + ], "controlState": "LEGACY_REINDEX", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -127,19 +130,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -190,7 +182,6 @@ Object { }, }, }, - "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, @@ -214,6 +205,9 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [], + ], "controlState": "LEGACY_DELETE", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -323,19 +317,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -390,7 +373,6 @@ Object { }, }, }, - "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, @@ -414,6 +396,9 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [], + ], "controlState": "LEGACY_DELETE", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -523,19 +508,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -594,7 +568,6 @@ Object { }, }, }, - "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, @@ -618,6 +591,9 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [], + ], "controlState": "DONE", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -727,19 +703,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -802,7 +767,6 @@ Object { }, }, }, - "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, @@ -864,6 +828,15 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [ + Object { + "index": Object { + "_id": "1234", + }, + }, + ], + ], "controlState": "LEGACY_DELETE", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -973,19 +946,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -1041,13 +1003,6 @@ Object { }, }, }, - "transformedDocBatches": Array [ - Array [ - Object { - "_id": "1234", - }, - ], - ], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, @@ -1071,6 +1026,15 @@ Object { "duration": 0, "state": Object { "batchSize": 1000, + "bulkOperationBatches": Array [ + Array [ + Object { + "index": Object { + "_id": "1234", + }, + }, + ], + ], "controlState": "FATAL", "currentAlias": ".my-so-index", "discardCorruptObjects": false, @@ -1180,19 +1144,8 @@ Object { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], @@ -1252,13 +1205,6 @@ Object { }, }, }, - "transformedDocBatches": Array [ - Array [ - Object { - "_id": "1234", - }, - ], - ], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", "waitForMigrationCompletion": false, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.test.ts index 06b5dd762cffc..ac1daf3c8761f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.test.ts @@ -40,7 +40,7 @@ describe('bulkOverwriteTransformedDocuments', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'new_index', - transformedDocs: [], + operations: [], refresh: 'wait_for', }); @@ -74,7 +74,7 @@ describe('bulkOverwriteTransformedDocuments', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'new_index', - transformedDocs: [], + operations: [], refresh: 'wait_for', }); @@ -99,7 +99,7 @@ describe('bulkOverwriteTransformedDocuments', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'new_index', - transformedDocs: [], + operations: [], refresh: 'wait_for', }); try { @@ -140,7 +140,7 @@ describe('bulkOverwriteTransformedDocuments', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'new_index', - transformedDocs: [], + operations: [], refresh: 'wait_for', }); @@ -193,7 +193,7 @@ describe('bulkOverwriteTransformedDocuments', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'new_index', - transformedDocs: [], + operations: [], refresh: 'wait_for', }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts index 7a6e8b2d9a5b5..716683c1938fb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts @@ -11,7 +11,6 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { errors as esErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, @@ -19,33 +18,13 @@ import { import { isWriteBlockException, isIndexNotFoundException } from './es_errors'; import { WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE } from './constants'; import type { TargetIndexHadWriteBlock, RequestEntityTooLargeException, IndexNotFound } from '.'; - -/** - * Given a document and index, creates a valid body for the Bulk API. - */ -export const createBulkOperationBody = (doc: SavedObjectsRawDoc, index: string) => { - return [ - { - index: { - _index: index, - _id: doc._id, - // overwrite existing documents - op_type: 'index', - // use optimistic concurrency control to ensure that outdated - // documents are only overwritten once with the latest version - if_seq_no: doc._seq_no, - if_primary_term: doc._primary_term, - }, - }, - doc._source, - ]; -}; +import type { BulkOperation } from '../model/create_batches'; /** @internal */ export interface BulkOverwriteTransformedDocumentsParams { client: ElasticsearchClient; index: string; - transformedDocs: SavedObjectsRawDoc[]; + operations: BulkOperation[]; refresh?: estypes.Refresh; } @@ -57,7 +36,7 @@ export const bulkOverwriteTransformedDocuments = ({ client, index, - transformedDocs, + operations, refresh = false, }: BulkOverwriteTransformedDocumentsParams): TaskEither.TaskEither< | RetryableEsClientError @@ -67,10 +46,6 @@ export const bulkOverwriteTransformedDocuments = 'bulk_index_succeeded' > => () => { - const body = transformedDocs.flatMap((doc) => { - return createBulkOperationBody(doc, index); - }); - return client .bulk({ // Because we only add aliases in the MARK_VERSION_INDEX_READY step we @@ -80,11 +55,13 @@ export const bulkOverwriteTransformedDocuments = // mappings. Such tampering could lead to many other problems and is // probably unlikely so for now we'll accept this risk and wait till // system indices puts in place a hard control. + index, require_alias: false, wait_for_active_shards: WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE, refresh, filter_path: ['items.*.error'], - body, + // we need to unwrap the existing BulkIndexOperationTuple's + operations: operations.flat(), }) .then((res) => { // Filter out version_conflict_engine_exception since these just mean diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.test.ts index 95a9a33831d09..da3c11686ec93 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.test.ts @@ -28,7 +28,7 @@ describe('calculateExcludeFilters', () => { expect(hook2).toHaveBeenCalledWith({ readonlyEsClient: { search: expect.any(Function) } }); expect(Either.isRight(result)).toBe(true); expect((result as Either.Right).right).toEqual({ - mustNotClauses: [ + filterClauses: [ { bool: { must: { term: { fieldA: '123' } } } }, { bool: { must: { term: { fieldB: 'abc' } } } }, ], @@ -49,7 +49,7 @@ describe('calculateExcludeFilters', () => { expect(Either.isRight(result)).toBe(true); expect((result as Either.Right).right).toEqual({ - mustNotClauses: [{ bool: { must: { term: { fieldB: 'abc' } } } }], + filterClauses: [{ bool: { must: { term: { fieldB: 'abc' } } } }], errorsByType: { type1: error }, }); }); @@ -91,7 +91,7 @@ describe('calculateExcludeFilters', () => { expect(Either.isRight(result)).toBe(true); expect((result as Either.Right).right).toEqual({ - mustNotClauses: [{ bool: { must: { term: { fieldB: 'abc' } } } }], + filterClauses: [{ bool: { must: { term: { fieldB: 'abc' } } } }], errorsByType: expect.any(Object), }); expect((result as Either.Right).right.errorsByType.type1.toString()).toMatchInlineSnapshot( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts index d0cf8f85fc497..30cc39d0a8fba 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts @@ -23,7 +23,7 @@ export interface CalculateExcludeFiltersParams { export interface CalculatedExcludeFilter { /** Array with all the clauses that must be bool.must_not'ed */ - mustNotClauses: QueryDslQueryContainer[]; + filterClauses: QueryDslQueryContainer[]; /** Any errors that were encountered during filter calculation, keyed by the type name */ errorsByType: Record; } @@ -91,17 +91,17 @@ export const calculateExcludeFilters = } const errorsByType: Array<[string, Error]> = []; - const mustNotClauses: QueryDslQueryContainer[] = []; + const filterClauses: QueryDslQueryContainer[] = []; // Loop through all results and collect successes and errors results.forEach((r) => Either.isRight(r) - ? mustNotClauses.push(r.right) + ? filterClauses.push(r.right) : Either.isLeft(r) && errorsByType.push([r.left.soType, r.left.error as Error]) ); return Either.right({ - mustNotClauses, + filterClauses, errorsByType: Object.fromEntries(errorsByType), }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts index 74dc39bc6fcd4..e483a16c270ff 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts @@ -116,7 +116,7 @@ export const checkForUnknownDocs = RetryableEsClientError, UnknownDocsFound | {} > => - async () => { + () => { const excludeQuery = addExcludedTypesToBoolQuery(knownTypes, excludeOnUpgradeQuery.bool); return getAggregatedTypesDocuments(client, indexName, excludeQuery) .then((unknownDocs) => { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.mocks.ts new file mode 100644 index 0000000000000..1b0e0a49e5062 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.mocks.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +export const emptyResponseClientMock = elasticsearchClientMock.createInternalClient( + Promise.resolve({ + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 0, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + }) +); + +export const initialExcludeOnUpgradeQueryMock = { + bool: { + must_not: [ + { + term: { + type: 'apm-services-telemetry', + }, + }, + { + term: { + type: 'application_usage_transactional', + }, + }, + { + term: { + type: 'background-session', + }, + }, + { + term: { + type: 'cases-sub-case', + }, + }, + { + term: { + type: 'csp_rule', + }, + }, + { + term: { + type: 'file-upload-telemetry', + }, + }, + { + term: { + type: 'fleet-agent-actions', + }, + }, + { + term: { + type: 'fleet-agent-events', + }, + }, + { + term: { + type: 'fleet-agents', + }, + }, + { + term: { + type: 'fleet-enrollment-api-keys', + }, + }, + { + term: { + type: 'guided-setup-state', + }, + }, + { + term: { + type: 'maps-telemetry', + }, + }, + { + term: { + type: 'ml-telemetry', + }, + }, + { + term: { + type: 'osquery-usage-metric', + }, + }, + { + term: { + type: 'server', + }, + }, + { + term: { + type: 'siem-detection-engine-rule-execution-info', + }, + }, + { + term: { + type: 'siem-detection-engine-rule-status', + }, + }, + { + term: { + type: 'timelion-sheet', + }, + }, + { + term: { + type: 'tsvb-validation-telemetry', + }, + }, + { + term: { + type: 'ui-counter', + }, + }, + ], + }, +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.test.ts new file mode 100644 index 0000000000000..af8fb8696d728 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.test.ts @@ -0,0 +1,276 @@ +/* + * Copyright 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { checkForUnknownDocs, type DocumentIdAndType } from './check_for_unknown_docs'; +import { cleanupUnknownAndExcluded } from './cleanup_unknown_and_excluded'; +import { calculateExcludeFilters } from './calculate_exclude_filters'; +import { deleteByQuery } from './delete_by_query'; +import { + emptyResponseClientMock, + initialExcludeOnUpgradeQueryMock, +} from './cleanup_unknown_and_excluded.mocks'; + +jest.mock('./check_for_unknown_docs'); +jest.mock('./calculate_exclude_filters'); +jest.mock('./delete_by_query'); + +const mockCheckForUnknownDocs = checkForUnknownDocs as jest.MockedFunction< + typeof checkForUnknownDocs +>; + +const mockCalculateExcludeFilters = calculateExcludeFilters as jest.MockedFunction< + typeof calculateExcludeFilters +>; + +const mockDeleteByQuery = deleteByQuery as jest.MockedFunction; + +describe('cleanupUnknownAndExcluded', () => { + const unknownDocs: DocumentIdAndType[] = [ + { id: 'dashboard:12345', type: 'dashboard' }, + { id: 'dashboard:67890', type: 'dashboard' }, + ]; + + const excludeFromUpgradeFilterHooks = { + 'search-session': async () => { + return { + bool: { + must: [ + { term: { type: 'search-session' } }, + { match: { 'search-session.persisted': false } }, + ], + }, + }; + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls `Actions.checkForUnknownDocs()` with the correct params', async () => { + mockCheckForUnknownDocs.mockReturnValueOnce(async () => Either.right({})); + mockCalculateExcludeFilters.mockReturnValueOnce(async () => + Either.right({ + filterClauses: [], + errorsByType: {}, + }) + ); + mockDeleteByQuery.mockReturnValueOnce(async () => + Either.right({ + taskId: '1234', + }) + ); + + const task = cleanupUnknownAndExcluded({ + client: emptyResponseClientMock, // the client will not be called anyway + indexName: '.kibana_8.0.0', + discardUnknownDocs: false, + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 50, + knownTypes: ['foo', 'bar'], + removedTypes: ['server', 'deprecated'], + }); + + await task(); + + expect(checkForUnknownDocs).toHaveBeenCalledTimes(1); + expect(checkForUnknownDocs).toHaveBeenCalledWith({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + knownTypes: ['foo', 'bar'], + }); + }); + + it('fails if there are unknown docs and `discardUnknownDocs === false`', async () => { + mockCheckForUnknownDocs.mockReturnValueOnce(async () => + Either.right({ + type: 'unknown_docs_found', + unknownDocs, + }) + ); + + const task = cleanupUnknownAndExcluded({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + discardUnknownDocs: false, + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 50, + knownTypes: ['foo', 'bar'], + removedTypes: ['server', 'deprecated'], + }); + + const result = await task(); + + expect(Either.isLeft(result)).toBe(true); + expect((result as Either.Left).left).toEqual({ + type: 'unknown_docs_found', + unknownDocs, + }); + expect(calculateExcludeFilters).not.toHaveBeenCalled(); + expect(deleteByQuery).not.toHaveBeenCalled(); + }); + + describe('if there are no unknown documents', () => { + it('calls `Actions.calculateExcludeFilters()` with the correct params', async () => { + mockCheckForUnknownDocs.mockReturnValueOnce(async () => Either.right({})); + mockCalculateExcludeFilters.mockReturnValueOnce(async () => + Either.right({ + filterClauses: [], + errorsByType: {}, + }) + ); + mockDeleteByQuery.mockReturnValueOnce(async () => + Either.right({ + taskId: '1234', + }) + ); + const task = cleanupUnknownAndExcluded({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + discardUnknownDocs: false, + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 50, + knownTypes: ['foo', 'bar'], + removedTypes: ['server', 'deprecated'], + }); + + await task(); + + expect(calculateExcludeFilters).toHaveBeenCalledTimes(1); + expect(calculateExcludeFilters).toHaveBeenCalledWith({ + client: emptyResponseClientMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 50, + }); + }); + }); + + describe('if there are unknown documents and `discardUnknownDocuments === true`', () => { + it('calls `Actions.calculateExcludeFilters()` with the correct params', async () => { + mockCheckForUnknownDocs.mockReturnValueOnce(async () => + Either.right({ + type: 'unknown_docs_found', + unknownDocs, + }) + ); + mockCalculateExcludeFilters.mockReturnValueOnce(async () => + Either.right({ + filterClauses: [], + errorsByType: {}, + }) + ); + mockDeleteByQuery.mockReturnValueOnce(async () => + Either.right({ + taskId: '1234', + }) + ); + const task = cleanupUnknownAndExcluded({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + discardUnknownDocs: true, + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 28, + knownTypes: ['foo', 'bar'], + removedTypes: ['server', 'deprecated'], + }); + + await task(); + + expect(calculateExcludeFilters).toHaveBeenCalledTimes(1); + expect(calculateExcludeFilters).toHaveBeenCalledWith({ + client: emptyResponseClientMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 28, + }); + }); + }); + + it('calls `deleteByQuery` with the correct params', async () => { + mockCheckForUnknownDocs.mockReturnValueOnce(async () => + Either.right({ + type: 'unknown_docs_found', + unknownDocs, + }) + ); + + const filterClauses: QueryDslQueryContainer[] = [ + { + bool: { + must: [ + { term: { type: 'search-session' } }, + { match: { 'search-session.persisted': false } }, + ], + }, + }, + ]; + + const errorsByType = { type1: new Error('an error!') }; + + mockCalculateExcludeFilters.mockReturnValueOnce(async () => + Either.right({ filterClauses, errorsByType }) + ); + mockDeleteByQuery.mockReturnValueOnce(async () => + Either.right({ + taskId: '1234', + }) + ); + const task = cleanupUnknownAndExcluded({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + discardUnknownDocs: true, + excludeOnUpgradeQuery: initialExcludeOnUpgradeQueryMock, + excludeFromUpgradeFilterHooks, + hookTimeoutMs: 28, + knownTypes: ['foo', 'bar'], + removedTypes: ['server', 'deprecated'], + }); + + const result = await task(); + + expect(deleteByQuery).toHaveBeenCalledTimes(1); + expect(deleteByQuery).toHaveBeenCalledWith({ + client: emptyResponseClientMock, + indexName: '.kibana_8.0.0', + query: { + bool: { + should: [ + // excluded from upgrade hook response + { + bool: { + must: [ + { term: { type: 'search-session' } }, + { match: { 'search-session.persisted': false } }, + ], + }, + }, + { term: { type: 'server' } }, // removed type + { term: { type: 'deprecated' } }, // removed type + { term: { type: 'dashboard' } }, // unknown type + ], + }, + }, + conflicts: 'proceed', + refresh: false, + }); + + expect(Either.isRight(result)).toBe(true); + expect((result as Either.Right).right).toEqual({ + type: 'cleanup_started' as const, + taskId: '1234', + unknownDocs, + errorsByType, + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.ts new file mode 100644 index 0000000000000..d7ceeec014ddc --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/cleanup_unknown_and_excluded.ts @@ -0,0 +1,133 @@ +/* + * Copyright 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 { pipe } from 'fp-ts/lib/function'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectTypeExcludeFromUpgradeFilterHook } from '@kbn/core-saved-objects-server'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import { + checkForUnknownDocs, + type DocumentIdAndType, + type UnknownDocsFound, +} from './check_for_unknown_docs'; +import { isTypeof } from '.'; +import { CalculatedExcludeFilter, calculateExcludeFilters } from './calculate_exclude_filters'; +import { deleteByQuery } from './delete_by_query'; + +/** @internal */ +export interface CleanupUnknownAndExcludedParams { + client: ElasticsearchClient; + indexName: string; + discardUnknownDocs: boolean; + excludeOnUpgradeQuery: QueryDslQueryContainer; + excludeFromUpgradeFilterHooks: Record; + hookTimeoutMs?: number; + knownTypes: string[]; + removedTypes: string[]; +} + +/** @internal */ +export interface CleanupStarted { + type: 'cleanup_started'; + /** Sample (1000 types * 100 docs per type) of the unknown documents that have been found */ + unknownDocs: DocumentIdAndType[]; + /** Any errors that were encountered during filter calculation, keyed by the type name */ + errorsByType: Record; + /** the id of the asynchronous delete task */ + taskId: string; +} + +/** + * Cleans up unknown and excluded types from the specified index. + */ +export const cleanupUnknownAndExcluded = ({ + client, + indexName, + discardUnknownDocs, + excludeOnUpgradeQuery, + excludeFromUpgradeFilterHooks, + hookTimeoutMs, + knownTypes, + removedTypes, +}: CleanupUnknownAndExcludedParams): TaskEither.TaskEither< + RetryableEsClientError | UnknownDocsFound, + CleanupStarted +> => { + let unknownDocs: DocumentIdAndType[] = []; + let unknownDocTypes: string[] = []; + let errorsByType: Record = {}; + + return pipe( + // check if there are unknown docs + checkForUnknownDocs({ client, indexName, knownTypes, excludeOnUpgradeQuery }), + + // make sure we are allowed to get rid of them (in case there are some) + TaskEither.chainEitherKW((unknownDocsRes: {} | UnknownDocsFound) => { + if (isTypeof(unknownDocsRes, 'unknown_docs_found')) { + unknownDocs = unknownDocsRes.unknownDocs; + unknownDocTypes = [...new Set(unknownDocs.map(({ type }) => type))]; + if (!discardUnknownDocs) { + return Either.left({ + type: 'unknown_docs_found' as const, + unknownDocs: unknownDocsRes.unknownDocs, + }); + } + } + return Either.right(undefined); + }), + + // calculate exclude filters (we use them to build the query for documents that must be deleted) + TaskEither.chainW( + (): TaskEither.TaskEither => + calculateExcludeFilters({ client, excludeFromUpgradeFilterHooks, hookTimeoutMs }) + ), + + // actively delete unwanted documents + TaskEither.chainW((excludeFiltersRes) => { + errorsByType = excludeFiltersRes.errorsByType; + + // we must delete everything that matches: + // - any of the plugin-defined exclude filters + // - OR any of the unknown types + const deleteQuery: QueryDslQueryContainer = { + bool: { + should: [ + ...excludeFiltersRes.filterClauses, + ...removedTypes.map((type) => ({ term: { type } })), + ...unknownDocTypes.map((type) => ({ term: { type } })), + ], + }, + }; + + return deleteByQuery({ + client, + indexName, + query: deleteQuery, + // we want to delete as many docs as we can in the current attempt + conflicts: 'proceed', + // instead of forcing refresh after each delete attempt, + // we opt for a delayRetry mechanism when conflicts appear, + // letting the periodic refresh kick in + refresh: false, + }); + }), + + // map response output + TaskEither.chainEitherKW((res) => { + return Either.right({ + type: 'cleanup_started' as const, + taskId: res.taskId, + unknownDocs, + errorsByType, + }); + }) + ); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.test.ts new file mode 100644 index 0000000000000..784edfdc67b6a --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; +import { errors as EsErrors } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { deleteByQuery } from './delete_by_query'; + +jest.mock('./catch_retryable_es_client_errors'); + +describe('deleteByQuery', () => { + const deleteQuery = { + bool: { + should: ['server', 'deprecated'].map((type) => ({ + term: { + type, + }, + })), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls catchRetryableEsClientErrors when the promise rejects', async () => { + // 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) + ); + + const task = deleteByQuery({ + client, + indexName: '.kibana_8.0.0', + query: deleteQuery, + conflicts: 'proceed', + refresh: true, + }); + try { + await task(); + } catch (e) { + /** ignore */ + } + expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); + }); + + it('calls `client.deleteByQuery` with the correct parameters', async () => { + const client = elasticsearchClientMock.createInternalClient( + Promise.resolve({ hits: { hits: [] } }) + ); + + const task = deleteByQuery({ + client, + indexName: '.kibana_8.0.0', + query: deleteQuery, + conflicts: 'proceed', + refresh: true, + }); + + await task(); + + expect(client.deleteByQuery).toHaveBeenCalledTimes(1); + expect(client.deleteByQuery).toHaveBeenCalledWith({ + index: '.kibana_8.0.0', + query: deleteQuery, + refresh: true, + wait_for_completion: false, + conflicts: 'proceed', + }); + }); + + it('resolves with `Either.right` if the delete task is successfully created', async () => { + const client = elasticsearchClientMock.createInternalClient( + Promise.resolve({ + took: 147, + timed_out: false, + task: 1234, + }) + ); + + const task = deleteByQuery({ + client, + indexName: '.kibana_8.0.0', + query: deleteQuery, + conflicts: 'proceed', + refresh: true, + }); + + const result = await task(); + + expect(Either.isRight(result)).toBe(true); + expect((result as Either.Right).right).toEqual({ taskId: '1234' }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.ts new file mode 100644 index 0000000000000..f9c426c26696d --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/delete_by_query.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 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 { Conflicts, QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { + catchRetryableEsClientErrors, + type RetryableEsClientError, +} from './catch_retryable_es_client_errors'; + +/** @internal */ +export interface DeleteByQueryParams { + client: ElasticsearchClient; + indexName: string; + query: QueryDslQueryContainer; + conflicts: Conflicts; + refresh?: boolean; +} + +/** @internal */ +export interface DeleteByQueryResponse { + taskId: string; +} + +/** + * Deletes documents matching the provided query + */ +export const deleteByQuery = + ({ + client, + indexName, + query, + conflicts, + refresh = false, + }: DeleteByQueryParams): TaskEither.TaskEither => + () => { + return client + .deleteByQuery({ + index: indexName, + query, + refresh, + conflicts, + wait_for_completion: false, + }) + .then(({ task: taskId }) => { + return Either.right({ taskId: String(taskId!) }); + }) + .catch(catchRetryableEsClientErrors); + }; 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 6b6db29563443..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'; @@ -76,13 +74,15 @@ import type { AliasNotFound, RemoveIndexNotAConcreteIndex } from './update_alias export type { AliasAction, UpdateAliasesParams } from './update_aliases'; export { updateAliases } from './update_aliases'; +export { cleanupUnknownAndExcluded } from './cleanup_unknown_and_excluded'; + +export { waitForDeleteByQueryTask } from './wait_for_delete_by_query_task'; + export type { CreateIndexParams } from './create_index'; 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 { @@ -91,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/actions/wait_for_delete_by_query_task.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_delete_by_query_task.test.ts new file mode 100644 index 0000000000000..8f9b60cfe02d1 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_delete_by_query_task.test.ts @@ -0,0 +1,199 @@ +/* + * Copyright 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 * as Option from 'fp-ts/lib/Option'; +import { errors as EsErrors, TransportResult } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { waitForDeleteByQueryTask } from './wait_for_delete_by_query_task'; +import { waitForTask } from './wait_for_task'; + +jest.mock('./wait_for_task'); + +const mockWaitForTask = waitForTask as jest.MockedFunction; + +describe('waitForDeleteByQueryTask', () => { + const client = elasticsearchClientMock.createInternalClient( + Promise.resolve(elasticsearchClientMock.createApiResponse({})) + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls waitForTask() with the appropriate params', async () => { + // Mock wait for delete finished successfully + mockWaitForTask.mockReturnValueOnce( + TaskEither.right({ + completed: true, + error: Option.none, + failures: Option.none, + description: 'some description', + }) + ); + + const task = waitForDeleteByQueryTask({ + client, + taskId: 'some task id', + timeout: '60s', + }); + + await task(); + + expect(waitForTask).toHaveBeenCalledWith({ + client, + taskId: 'some task id', + timeout: '60s', + }); + }); + + describe('when waitForTask() method rejects with a task completion timeout error', () => { + it('catches the error and returns the appropriate Left response', async () => { + // Mock task completion error + const error = createError({ + body: { error: { type: 'timeout_exception', reason: 'es_reason' } }, + }); + + mockWaitForTask.mockReturnValueOnce( + TaskEither.left({ + type: 'wait_for_task_completion_timeout' as const, + message: '[timeout_exception] es_reason', + error, + }) + ); + + const task = waitForDeleteByQueryTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + const res = await task(); + + expect(res).toEqual( + Either.left({ + type: 'wait_for_task_completion_timeout' as const, + message: '[timeout_exception] es_reason', + error, + }) + ); + }); + }); + + describe('when waitForTask() method rejects with a retryable error', () => { + it('catches the error and returns the appropriate Left response', async () => { + // Mock retryable error + const error = createError({ + statusCode: 503, + body: { error: { type: 'es_type', reason: 'es_reason' } }, + }); + + mockWaitForTask.mockReturnValueOnce( + TaskEither.left({ + type: 'retryable_es_client_error' as const, + message: 'es_type', + error, + }) + ); + + const task = waitForDeleteByQueryTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + const res = await task(); + expect(res).toEqual( + Either.left({ + type: 'retryable_es_client_error' as const, + message: 'es_type', + error, + }) + ); + }); + }); + + describe('when waitForTask() method finishes successfully, but there are failures', () => { + it('returns a Left response, with the list of failures', async () => { + // Mock successful with failures + const failures = ['dashboard:12345 - Failed to delete', 'dashboard:67890 - Failed to delete']; + + mockWaitForTask.mockReturnValueOnce( + TaskEither.right({ + completed: true, + failures: Option.some(failures), + error: Option.none, + }) + ); + + const task = waitForDeleteByQueryTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + const res = await task(); + expect(res).toEqual( + Either.left({ + type: 'cleanup_failed' as const, + failures, + }) + ); + }); + }); + + describe('when waitForTask() method throws an unexpected error', () => { + it('rethrows the error', async () => { + // Mock unexpected 500 Server Error + const error = createError({ + statusCode: 500, + body: { error: { type: 'server_error', reason: 'Something really bad happened' } }, + }); + + mockWaitForTask.mockReturnValueOnce(async () => { + throw error; + }); + + const task = waitForDeleteByQueryTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + expect(task()).rejects.toEqual(error); + }); + }); + + describe('when waitForTask() method finishes successfully without failures', () => { + it('finsihes with a cleanup_successful Right clause', async () => { + // Mock wait for delete finished successfully + mockWaitForTask.mockReturnValueOnce( + TaskEither.right({ + completed: true, + error: Option.none, + failures: Option.none, + description: 'some description', + }) + ); + const task = waitForDeleteByQueryTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + const res = await task(); + + expect(res).toEqual(Either.right({ type: 'cleanup_successful' as const })); + }); + }); +}); + +const createError = (esResponse: Partial) => { + return new EsErrors.ResponseError(elasticsearchClientMock.createApiResponse(esResponse)); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_delete_by_query_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_delete_by_query_task.ts new file mode 100644 index 0000000000000..5fae1283b083b --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_delete_by_query_task.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 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 TaskEither from 'fp-ts/lib/TaskEither'; +import * as Option from 'fp-ts/lib/Option'; +import { flow } from 'fp-ts/lib/function'; +import { waitForTask } from './wait_for_task'; + +/** @internal */ +export interface CleanupErrorResponse { + type: 'cleanup_failed'; + failures: string[]; + versionConflicts?: number; +} + +/** @internal */ +export interface CleanupSuccessfulResponse { + type: 'cleanup_successful'; + deleted?: number; +} + +export const waitForDeleteByQueryTask = flow( + waitForTask, + TaskEither.chainW( + (res): TaskEither.TaskEither => { + if (Option.isSome(res.failures) || res.response?.version_conflicts) { + return TaskEither.left({ + type: 'cleanup_failed' as const, + failures: Option.isSome(res.failures) ? res.failures.value : [], + versionConflicts: res.response?.version_conflicts, + }); + } else if (Option.isSome(res.error)) { + throw new Error( + 'waitForDeleteByQueryTask task failed with the following error:\n' + + JSON.stringify(res.error.value) + ); + } else { + return TaskEither.right({ + type: 'cleanup_successful' as const, + deleted: res.response?.deleted, + }); + } + } + ) +); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_pickup_updated_mappings_task.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_pickup_updated_mappings_task.test.ts index 896a687c1f8d3..66d7689a0b9e4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_pickup_updated_mappings_task.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_pickup_updated_mappings_task.test.ts @@ -10,32 +10,33 @@ 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 { waitForPickupUpdatedMappingsTask } from './wait_for_pickup_updated_mappings_task'; -import { setWriteBlock } from './set_write_block'; -jest.mock('./catch_retryable_es_client_errors'); +jest.mock('./catch_retryable_es_client_errors', () => { + const { catchRetryableEsClientErrors: actualImplementation } = jest.requireActual( + './catch_retryable_es_client_errors' + ); + return { + catchRetryableEsClientErrors: jest.fn(actualImplementation), + }; +}); describe('waitForPickupUpdatedMappingsTask', () => { 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) - ); - - const nonRetryableError = new Error('crash'); - const clientWithNonRetryableError = elasticsearchClientMock.createInternalClient( - elasticsearchClientMock.createErrorTransportRequestPromise(nonRetryableError) - ); it('calls catchRetryableEsClientErrors when the promise rejects', async () => { + // 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) + ); const task = waitForPickupUpdatedMappingsTask({ client, taskId: 'my task id', @@ -50,11 +51,16 @@ describe('waitForPickupUpdatedMappingsTask', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = setWriteBlock({ - client: clientWithNonRetryableError, - index: 'my_index', + const nonRetryableError = new Error('crash'); + const client = elasticsearchClientMock.createInternalClient( + elasticsearchClientMock.createErrorTransportRequestPromise(nonRetryableError) + ); + + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: 'my task id', + timeout: '2m', }); - await task(); - expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); + expect(task()).rejects.toThrowError(nonRetryableError); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.test.ts index 4a5fc20e1fe12..61611dc5afc81 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.test.ts @@ -5,44 +5,113 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { errors as EsErrors } from '@elastic/elasticsearch'; -import { waitForTask } from './wait_for_task'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; -jest.mock('./catch_retryable_es_client_errors'); +import * as Either from 'fp-ts/lib/Either'; +import { errors as EsErrors, TransportResult } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { waitForTask } from './wait_for_task'; describe('waitForTask', () => { beforeEach(() => { jest.clearAllMocks(); }); - // Create a mock client that rejects all methods with a 503 status code - // response. - const retryableError = new EsErrors.ResponseError( - elasticsearchClientMock.createApiResponse({ + it('calls tasks API get() with the correct parameters', async () => { + // Mock client that rejects with a retryable error + const { client } = createErrorClient({ statusCode: 503, body: { error: { type: 'es_type', reason: 'es_reason' } }, - }) - ); - const client = elasticsearchClientMock.createInternalClient( - elasticsearchClientMock.createErrorTransportRequestPromise(retryableError) - ); + }); + + const task = waitForTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + await task(); + expect(client.tasks.get).toHaveBeenCalledTimes(1); + expect(client.tasks.get).toHaveBeenCalledWith({ + task_id: 'my task id', + wait_for_completion: true, + timeout: '60s', + }); + }); + + describe('when tasks API get() method rejects with a task completion timeout error', () => { + it('catches the error and returns the appropriate Left response', async () => { + // Mock client that rejects with a task completion timeout error + const { client, error } = createErrorClient({ + body: { error: { type: 'timeout_exception', reason: 'es_reason' } }, + }); + + const task = waitForTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + const res = await task(); + + expect(res).toEqual( + Either.left({ + type: 'wait_for_task_completion_timeout' as const, + message: '[timeout_exception] es_reason', + error, + }) + ); + }); + }); + + describe('when tasks API get() method rejects with a retryable error', () => { + it('catches the error and returns the appropriate Left response', async () => { + // Mock client that rejects with a 503 status code + const { client, error } = createErrorClient({ + statusCode: 503, + body: { error: { type: 'es_type', reason: 'es_reason' } }, + }); - describe('waitForPickupUpdatedMappingsTask', () => { - it('calls catchRetryableEsClientErrors when the promise rejects', async () => { const task = waitForTask({ client, taskId: 'my task id', timeout: '60s', }); - try { - await task(); - } catch (e) { - /** ignore */ - } - expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); + const res = await task(); + expect(res).toEqual( + Either.left({ + type: 'retryable_es_client_error' as const, + message: 'es_type', + error, + }) + ); + }); + }); + + describe('when tasks API get() method rejects with an unexpected error', () => { + it('rethrows the error', async () => { + // Mock client that rejects with a 500 Server Error + const { client, error } = createErrorClient({ + statusCode: 500, + body: { error: { type: 'server_error', reason: 'Something really bad happened' } }, + }); + + const task = waitForTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); + + expect(task()).rejects.toEqual(error); }); }); }); + +const createErrorClient = (esResponse: Partial) => { + const error = new EsErrors.ResponseError(elasticsearchClientMock.createApiResponse(esResponse)); + const client = elasticsearchClientMock.createInternalClient( + elasticsearchClientMock.createErrorTransportRequestPromise(error) + ); + + return { client, error }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts index 8bb3abaec87db..86389f01727b7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/types'; import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; @@ -22,6 +22,7 @@ export interface WaitForTaskResponse { completed: boolean; failures: Option.Option; description?: string; + response?: estypes.TasksTaskStatus; } /** @@ -90,6 +91,7 @@ export const waitForTask = error: Option.fromNullable(body.error as estypes.ErrorCauseKeys), failures: failures.length > 0 ? Option.some(failures) : Option.none, description: body.task.description, + response: body.response, }); }) .catch(catchWaitForTaskCompletionTimeout) diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/index.ts new file mode 100644 index 0000000000000..962f40b87db02 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { logActionResponse, logStateTransition, type LogAwareState } from './logs'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/logs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/logs.ts new file mode 100644 index 0000000000000..2f3a1459f594c --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/common/utils/logs.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 type { Logger, LogMeta } from '@kbn/logging'; +import { MigrationLog } from '../../types'; + +export interface LogAwareState { + controlState: string; + logs: MigrationLog[]; +} + +interface StateTransitionLogMeta extends LogMeta { + kibana: { + migrations: { + state: LogAwareState; + duration: number; + }; + }; +} + +export const logStateTransition = ( + logger: Logger, + logMessagePrefix: string, + prevState: LogAwareState, + currState: LogAwareState, + tookMs: number +) => { + if (currState.logs.length > prevState.logs.length) { + currState.logs.slice(prevState.logs.length).forEach(({ message, level }) => { + switch (level) { + case 'error': + return logger.error(logMessagePrefix + message); + case 'warning': + return logger.warn(logMessagePrefix + message); + case 'info': + return logger.info(logMessagePrefix + message); + default: + throw new Error(`unexpected log level ${level}`); + } + }); + } + + logger.info( + logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.` + ); + logger.debug( + logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.`, + { + kibana: { + migrations: { + state: currState, + duration: tookMs, + }, + }, + } + ); +}; + +export const logActionResponse = ( + logger: Logger, + logMessagePrefix: string, + state: LogAwareState, + res: unknown +) => { + logger.debug(logMessagePrefix + `${state.controlState} RESPONSE`, res as LogMeta); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts new file mode 100644 index 0000000000000..6c78ae52550c5 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal'; + +/** + * Merge mappings from all registered saved object types. + */ +export const buildTypesMappings = ( + types: SavedObjectsType[] +): SavedObjectsTypeMappingDefinitions => { + return types.reduce((acc, { name: type, mappings }) => { + const duplicate = acc.hasOwnProperty(type); + if (duplicate) { + throw new Error(`Type ${type} is already defined.`); + } + return { + ...acc, + [type]: mappings, + }; + }, {}); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/index.ts index 81bacea4a0cbd..a113e5e5f77bc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/index.ts @@ -11,6 +11,8 @@ export type { LogFn } from './migration_logger'; export { excludeUnusedTypesQuery, REMOVED_TYPES } from './unused_types'; export { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; export { deterministicallyRegenerateObjectId } from './regenerate_object_id'; +export { buildTypesMappings } from './build_types_mappings'; +export { createIndexMap, type IndexMap, type CreateIndexMapOptions } from './build_index_map'; export type { DocumentsTransformFailed, DocumentsTransformSuccess, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts index e506ff40073fc..46ddd23251217 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts @@ -45,12 +45,10 @@ export const REMOVED_TYPES: string[] = [ 'maps-telemetry', // Deprecated, no longer used since 8.7 https://github.com/elastic/kibana/pull/148530 'csp_rule', + // Removed in 8.8 https://github.com/elastic/kibana/pull/151116 + 'upgrade-assistant-telemetry', ].sort(); -// When migrating from the outdated index we use a read query which excludes -// saved objects which are no longer used. These saved objects will still be -// kept in the outdated index for backup purposes, but won't be available in -// the upgraded index. export const excludeUnusedTypesQuery: QueryDslQueryContainer = { bool: { must_not: [ @@ -59,23 +57,6 @@ export const excludeUnusedTypesQuery: QueryDslQueryContainer = { type: typeName, }, })), - // https://github.com/elastic/kibana/issues/96131 - { - bool: { - must: [ - { - match: { - type: 'search-session', - }, - }, - { - match: { - 'search-session.persisted': false, - }, - }, - ], - }, - }, ], }, }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts index b50f64a26620d..97e62e8657d5e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { KibanaMigrator, mergeTypes } from './kibana_migrator'; +export { KibanaMigrator } from './kibana_migrator'; export type { KibanaMigratorOptions } from './kibana_migrator'; -export { buildActiveMappings } from './core'; +export { buildActiveMappings, buildTypesMappings } from './core'; export { DocumentMigrator } from './document_migrator'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index 27798706e8fd1..2961600edd61d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -164,19 +164,8 @@ describe('createInitialState', () => { }, }, Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, - }, - Object { - "match": Object { - "search-session.persisted": false, - }, - }, - ], + "term": Object { + "type": "upgrade-assistant-telemetry", }, }, ], diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 655f164b831cf..f5a462ebcedac 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -281,6 +281,7 @@ const mockOptions = () => { ]), kibanaIndex: '.my-index', soMigrationsConfig: { + algorithm: 'v2', batchSize: 20, maxBatchSizeBytes: ByteSizeValue.parse('20mb'), pollInterval: 20000, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index fa0c88629758b..408b277444995 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -16,7 +16,6 @@ import Semver from 'semver'; import type { Logger } from '@kbn/logging'; import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import type { SavedObjectUnsanitizedDoc, SavedObjectsRawDoc, @@ -31,11 +30,12 @@ import { type KibanaMigratorStatus, type MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; -import { buildActiveMappings } from './core'; +import { buildActiveMappings, buildTypesMappings } from './core'; import { DocumentMigrator, type VersionedTransformer } from './document_migrator'; import { createIndexMap } from './core/build_index_map'; import { runResilientMigrator } from './run_resilient_migrator'; import { migrateRawDocsSafely } from './core/migrate_raw_docs'; +import { runZeroDowntimeMigration } from './zdt'; // ensure plugins don't try to convert SO namespaceTypes after 8.0.0 // see https://github.com/elastic/kibana/issues/147344 @@ -91,7 +91,7 @@ export class KibanaMigrator implements IKibanaMigrator { this.soMigrationsConfig = soMigrationsConfig; this.typeRegistry = typeRegistry; this.serializer = new SavedObjectsSerializer(this.typeRegistry); - this.mappingProperties = mergeTypes(this.typeRegistry.getAllTypes()); + this.mappingProperties = buildTypesMappings(this.typeRegistry.getAllTypes()); this.log = logger; this.kibanaVersion = kibanaVersion; this.documentMigrator = new DocumentMigrator({ @@ -135,6 +135,28 @@ export class KibanaMigrator implements IKibanaMigrator { } private runMigrationsInternal(): Promise { + const migrationAlgorithm = this.soMigrationsConfig.algorithm; + if (migrationAlgorithm === 'zdt') { + return this.runMigrationZdt(); + } else { + return this.runMigrationV2(); + } + } + + private runMigrationZdt(): Promise { + return runZeroDowntimeMigration({ + kibanaIndexPrefix: this.kibanaIndex, + typeRegistry: this.typeRegistry, + logger: this.log, + documentMigrator: this.documentMigrator, + migrationConfig: this.soMigrationsConfig, + docLinks: this.docLinks, + serializer: this.serializer, + elasticsearchClient: this.client, + }); + } + + private runMigrationV2(): Promise { const indexMap = createIndexMap({ kibanaIndexName: this.kibanaIndex, indexMap: this.mappingProperties, @@ -187,20 +209,3 @@ export class KibanaMigrator implements IKibanaMigrator { return this.documentMigrator.migrate(doc); } } - -/** - * Merges savedObjectMappings properties into a single object, verifying that - * no mappings are redefined. - */ -export function mergeTypes(types: SavedObjectsType[]): SavedObjectsTypeMappingDefinitions { - return types.reduce((acc, { name: type, mappings }) => { - const duplicate = acc.hasOwnProperty(type); - if (duplicate) { - throw new Error(`Type ${type} is already defined.`); - } - return { - ...acc, - [type]: mappings, - }; - }, {}); -} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts index 255a26275b6e8..48a3bad0d0960 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts @@ -17,7 +17,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; import { errors } from '@elastic/elasticsearch'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { AllControlStates, State } from './state'; +import type { AllControlStates, State } from './state'; import { createInitialState } from './initial_state'; import { ByteSizeValue } from '@kbn/config-schema'; @@ -44,6 +44,7 @@ describe('migrationsStateActionMachine', () => { migrationVersionPerType: {}, indexPrefix: '.my-so-index', migrationsConfig: { + algorithm: 'v2', batchSize: 1000, maxBatchSizeBytes: new ByteSizeValue(1e8), pollInterval: 0, @@ -102,7 +103,9 @@ describe('migrationsStateActionMachine', () => { ...initialState, reason: 'the fatal reason', outdatedDocuments: [{ _id: '1234', password: 'sensitive password' }], - transformedDocBatches: [[{ _id: '1234', password: 'sensitive transformed password' }]], + bulkOperationBatches: [ + [[{ index: { _id: '1234' } }, { password: 'sensitive transformed password' }]], + ], } as State, logger: mockLogger.get(), model: transitionModel(['LEGACY_DELETE', 'FATAL']), diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts index ef9db961c8112..1b5caf3c4e75d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts @@ -8,72 +8,19 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; -import type { Logger, LogMeta } from '@kbn/logging'; +import type { Logger } from '@kbn/logging'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { getErrorMessage, getRequestDebugMeta, } from '@kbn/core-elasticsearch-client-server-internal'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { BulkOperationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { logActionResponse, logStateTransition } from './common/utils/logs'; import { type Model, type Next, stateActionMachine } from './state_action_machine'; import { cleanup } from './migrations_state_machine_cleanup'; import type { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; - -interface StateTransitionLogMeta extends LogMeta { - kibana: { - migrations: { - state: State; - duration: number; - }; - }; -} - -const logStateTransition = ( - logger: Logger, - logMessagePrefix: string, - prevState: State, - currState: State, - tookMs: number -) => { - if (currState.logs.length > prevState.logs.length) { - currState.logs.slice(prevState.logs.length).forEach(({ message, level }) => { - switch (level) { - case 'error': - return logger.error(logMessagePrefix + message); - case 'warning': - return logger.warn(logMessagePrefix + message); - case 'info': - return logger.info(logMessagePrefix + message); - default: - throw new Error(`unexpected log level ${level}`); - } - }); - } - - logger.info( - logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.` - ); - logger.debug( - logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.`, - { - kibana: { - migrations: { - state: currState, - duration: tookMs, - }, - }, - } - ); -}; - -const logActionResponse = ( - logger: Logger, - logMessagePrefix: string, - state: State, - res: unknown -) => { - logger.debug(logMessagePrefix + `${state.controlState} RESPONSE`, res as LogMeta); -}; +import type { BulkOperation } from './model/create_batches'; /** * A specialized migrations-specific state-action machine that: @@ -128,9 +75,9 @@ export async function migrationStateActionMachine({ ), }, ...{ - transformedDocBatches: ( - (newState as ReindexSourceToTempIndexBulk).transformedDocBatches ?? [] - ).map((batches) => batches.map((doc) => ({ _id: doc._id }))) as [SavedObjectsRawDoc[]], + bulkOperationBatches: redactBulkOperationBatches( + (newState as ReindexSourceToTempIndexBulk).bulkOperationBatches ?? [[]] + ), }, }; @@ -212,3 +159,11 @@ export async function migrationStateActionMachine({ } } } + +const redactBulkOperationBatches = ( + bulkOperationBatches: BulkOperation[][] +): BulkOperationContainer[][] => { + return bulkOperationBatches.map((batch) => + batch.map((operation) => (Array.isArray(operation) ? operation[0] : operation)) + ); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts index 8b1f93c327857..c3f6016ea9d9b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts @@ -8,10 +8,13 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import * as Actions from './actions'; -import type { State } from './state'; -export async function cleanup(client: ElasticsearchClient, state?: State) { - if (!state) return; +type CleanableState = { sourceIndexPitId: string } | {}; + +export async function cleanup(client: ElasticsearchClient, state?: CleanableState) { + if (!state) { + return; + } if ('sourceIndexPitId' in state) { await Actions.closePit({ client, pitId: state.sourceIndexPitId })(); } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts index ec9afc31f90d1..3ae3b1c7f20d8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts @@ -10,8 +10,13 @@ import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { createBatches } from './create_batches'; describe('createBatches', () => { - const DOCUMENT_SIZE_BYTES = 128; - const INDEX = '.kibana_version_index'; + const documentToOperation = (document: SavedObjectsRawDoc) => [ + { index: { _id: document._id } }, + document._source, + ]; + + const DOCUMENT_SIZE_BYTES = 77; // 76 + \n + it('returns right one batch if all documents fit in maxBatchSizeBytes', () => { const documents = [ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, @@ -19,8 +24,8 @@ describe('createBatches', () => { { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, ]; - expect(createBatches(documents, INDEX, DOCUMENT_SIZE_BYTES * 3)).toEqual( - Either.right([documents]) + expect(createBatches({ documents, maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 3 })).toEqual( + Either.right([documents.map(documentToOperation)]) ); }); it('creates multiple batches with each batch limited to maxBatchSizeBytes', () => { @@ -31,32 +36,36 @@ describe('createBatches', () => { { _id: '', _source: { type: 'dashboard', title: 'my saved object title 44' } }, { _id: '', _source: { type: 'dashboard', title: 'my saved object title 55' } }, ]; - expect(createBatches(documents, INDEX, DOCUMENT_SIZE_BYTES * 2)).toEqual( - Either.right([[documents[0], documents[1]], [documents[2], documents[3]], [documents[4]]]) + expect(createBatches({ documents, maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 2 })).toEqual( + Either.right([ + documents.slice(0, 2).map(documentToOperation), + documents.slice(2, 4).map(documentToOperation), + documents.slice(4).map(documentToOperation), + ]) ); }); it('creates a single empty batch if there are no documents', () => { const documents = [] as SavedObjectsRawDoc[]; - expect(createBatches(documents, INDEX, 100)).toEqual(Either.right([[]])); + expect(createBatches({ documents, maxBatchSizeBytes: 100 })).toEqual(Either.right([[]])); }); it('throws if any one document exceeds the maxBatchSizeBytes', () => { const documents = [ - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, + { _id: 'foo', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, { - _id: '', + _id: 'bar', _source: { type: 'dashboard', title: 'my saved object title ² with a very long title that exceeds max size bytes', }, }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, + { _id: 'baz', _source: { type: 'dashboard', title: 'my saved object title ®' } }, ]; - expect(createBatches(documents, INDEX, 178)).toEqual( + expect(createBatches({ documents, maxBatchSizeBytes: 120 })).toEqual( Either.left({ - maxBatchSizeBytes: 178, - docSizeBytes: 179, + maxBatchSizeBytes: 120, + docSizeBytes: 130, type: 'document_exceeds_batch_size_bytes', - document: documents[1], + documentId: documents[1]._id, }) ); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts index a591505f542c7..ec19f834d9ceb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts @@ -7,27 +7,50 @@ */ import * as Either from 'fp-ts/lib/Either'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { createBulkOperationBody } from '../actions/bulk_overwrite_transformed_documents'; +import type { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; +import type { BulkOperationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { createBulkDeleteOperationBody, createBulkIndexOperationTuple } from './helpers'; +import type { TransformErrorObjects } from '../core'; + +export type BulkIndexOperationTuple = [BulkOperationContainer, SavedObjectsRawDocSource]; +export type BulkOperation = BulkIndexOperationTuple | BulkOperationContainer; + +export interface CreateBatchesParams { + documents: SavedObjectsRawDoc[]; + corruptDocumentIds?: string[]; + transformErrors?: TransformErrorObjects[]; + maxBatchSizeBytes: number; +} + +export interface DocumentExceedsBatchSize { + documentId: string; + type: 'document_exceeds_batch_size_bytes'; + docSizeBytes: number; + maxBatchSizeBytes: number; +} /** * Creates batches of documents to be used by the bulk API. Each batch will * have a request body content length that's <= maxBatchSizeBytes */ -export function createBatches( - docs: SavedObjectsRawDoc[], - index: string, - maxBatchSizeBytes: number -) { +export function createBatches({ + documents, + corruptDocumentIds = [], + transformErrors = [], + maxBatchSizeBytes, +}: CreateBatchesParams): Either.Either { /* To build up the NDJSON request body we construct an array of objects like: * [ * {"index": ...} * {"title": "my saved object"} + * {"delete": ...} + * {"delete": ...} * ... * ] - * However, when we call JSON.stringify on this array the resulting string - * will be surrounded by `[]` which won't be present in the NDJSON so these - * two characters need to be removed from the size calculation. + * For indexing operations, createBulkIndexOperationTuple + * returns a tuple of the form [{operation, id}, {document}] + * Thus, for batch size calculations, we must take into account + * that this tuple's surrounding brackets `[]` won't be present in the NDJSON */ const BRACKETS_BYTES = 2; /* Each document in the NDJSON (including the last one) needs to be @@ -36,29 +59,68 @@ export function createBatches( */ const NDJSON_NEW_LINE_BYTES = 1; - const batches = [[]] as [SavedObjectsRawDoc[]]; + const BASE_DELETE_OPERATION_SIZE = Buffer.byteLength( + JSON.stringify(createBulkDeleteOperationBody('')), + 'utf8' + ); + + const batches: BulkOperation[][] = [[]]; let currBatch = 0; let currBatchSizeBytes = 0; - for (const doc of docs) { - const bulkOperationBody = createBulkOperationBody(doc, index); + + // group operations in batches of at most maxBatchSize + const assignToBatch = ( + operation: BulkOperationContainer | BulkIndexOperationTuple, + operationSizeBytes: number + ): boolean => { + operationSizeBytes += NDJSON_NEW_LINE_BYTES; + + if (operationSizeBytes > maxBatchSizeBytes) { + // the current operation (+ payload) does not even fit a single batch, fail! + return false; + } else if (currBatchSizeBytes + operationSizeBytes <= maxBatchSizeBytes) { + batches[currBatch].push(operation); + currBatchSizeBytes = currBatchSizeBytes + operationSizeBytes; + } else { + currBatch++; + batches[currBatch] = [operation]; + currBatchSizeBytes = operationSizeBytes; + } + return true; + }; + + // create index (update) operations for all transformed documents + for (const document of documents) { + const bulkIndexOperationBody = createBulkIndexOperationTuple(document); + // take into account that this tuple's surrounding brackets `[]` won't be present in the NDJSON const docSizeBytes = - Buffer.byteLength(JSON.stringify(bulkOperationBody), 'utf8') - - BRACKETS_BYTES + - NDJSON_NEW_LINE_BYTES; - if (docSizeBytes > maxBatchSizeBytes) { + Buffer.byteLength(JSON.stringify(bulkIndexOperationBody), 'utf8') - BRACKETS_BYTES; + if (!assignToBatch(bulkIndexOperationBody, docSizeBytes)) { return Either.left({ - type: 'document_exceeds_batch_size_bytes', + documentId: document._id, + type: 'document_exceeds_batch_size_bytes' as const, + docSizeBytes, + maxBatchSizeBytes, + }); + } + } + + // create delete operations for all corrupt documents + transform errors + const unwantedDocumentIds = [ + ...corruptDocumentIds, + ...transformErrors.map(({ rawId: documentId }) => documentId), + ]; + + for (const documentId of unwantedDocumentIds) { + const bulkDeleteOperationBody = createBulkDeleteOperationBody(documentId); + const docSizeBytes = BASE_DELETE_OPERATION_SIZE + Buffer.byteLength(documentId, 'utf8'); + if (!assignToBatch(bulkDeleteOperationBody, docSizeBytes)) { + return Either.left({ + documentId, + type: 'document_exceeds_batch_size_bytes' as const, docSizeBytes, maxBatchSizeBytes, - document: doc, }); - } else if (currBatchSizeBytes + docSizeBytes <= maxBatchSizeBytes) { - batches[currBatch].push(doc); - currBatchSizeBytes = currBatchSizeBytes + docSizeBytes; - } else { - currBatch++; - batches[currBatch] = [doc]; - currBatchSizeBytes = docSizeBytes; } } 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 a7e71bc99e9e0..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 @@ -8,27 +8,29 @@ import { gt, valid } from 'semver'; import type { + BulkOperationContainer, QueryDslBoolQuery, QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/types'; import * as Either from 'fp-ts/lib/Either'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import type { State } from '../state'; import type { AliasAction, FetchIndexResponse } from '../actions'; +import type { BulkIndexOperationTuple } from './create_batches'; /** * A helper function/type for ensuring that all control state's are handled. */ export function throwBadControlState(p: never): never; -export function throwBadControlState(controlState: any) { +export function throwBadControlState(controlState: unknown) { throw new Error('Unexpected control state: ' + controlState); } /** * A helper function/type for ensuring that all response types are handled. */ -export function throwBadResponse(state: State, p: never): never; -export function throwBadResponse(state: State, res: any): never { +export function throwBadResponse(state: { controlState: string }, p: never): never; +export function throwBadResponse(state: { controlState: string }, res: unknown): never { throw new Error( `${state.controlState} received unexpected action response: ` + JSON.stringify(res) ); @@ -105,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[] = []; @@ -119,7 +121,7 @@ export function addMustClausesToBoolQuery( must = must.concat(boolQuery.must); } - must.push(...mustClauses); + must.push(...filterClauses); return { bool: { @@ -131,12 +133,12 @@ export function addMustClausesToBoolQuery( /** * Add the given clauses to the 'must_not' of the given query + * @param filterClauses the clauses to be added to a 'must_not' * @param boolQuery the bool query to be enriched - * @param mustNotClauses the clauses to be added to a 'must_not' * @returns a new query container with the enriched query */ export function addMustNotClausesToBoolQuery( - mustNotClauses: QueryDslQueryContainer[], + filterClauses: QueryDslQueryContainer[], boolQuery?: QueryDslBoolQuery ): QueryDslQueryContainer { let mustNot: QueryDslQueryContainer[] = []; @@ -145,7 +147,7 @@ export function addMustNotClausesToBoolQuery( mustNot = mustNot.concat(boolQuery.must_not); } - mustNot.push(...mustNotClauses); + mustNot.push(...filterClauses); return { bool: { @@ -205,3 +207,28 @@ export function buildRemoveAliasActions( return [{ remove: { index, alias, must_exist: true } }]; }); } + +/** + * Given a document, creates a valid body to index the document using the Bulk API. + */ +export const createBulkIndexOperationTuple = (doc: SavedObjectsRawDoc): BulkIndexOperationTuple => { + return [ + { + index: { + _id: doc._id, + // use optimistic concurrency control to ensure that outdated + // documents are only overwritten once with the latest version + ...(typeof doc._seq_no !== 'undefined' && { if_seq_no: doc._seq_no }), + ...(typeof doc._primary_term !== 'undefined' && { if_primary_term: doc._primary_term }), + }, + }, + doc._source, + ]; +}; + +/** + * Given a document id, creates a valid body to delete the document using the Bulk API. + */ +export const createBulkDeleteOperationBody = (_id: string): BulkOperationContainer => ({ + delete: { _id }, +}); 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 3c47ea01ecd72..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 @@ -11,48 +11,52 @@ import * as Option from 'fp-ts/lib/Option'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import type { + BaseState, + CalculateExcludeFiltersState, + UpdateSourceMappingsState, + CheckTargetMappingsState, + CheckUnknownDocumentsState, + CheckVersionIndexReadyActions, + CleanupUnknownAndExcluded, + CleanupUnknownAndExcludedWaitForTaskState, + CloneTempToSource, + CreateNewTargetState, + CreateReindexTempState, FatalState, - State, - LegacySetWriteBlockState, - SetSourceWriteBlockState, LegacyCreateReindexTargetState, + LegacyDeleteState, LegacyReindexState, LegacyReindexWaitForTaskState, - LegacyDeleteState, - ReindexSourceToTempOpenPit, - ReindexSourceToTempRead, - ReindexSourceToTempClosePit, - ReindexSourceToTempTransform, - RefreshTarget, - UpdateTargetMappingsState, - UpdateTargetMappingsWaitForTaskState, + LegacySetWriteBlockState, + MarkVersionIndexReady, + MarkVersionIndexReadyConflict, + OutdatedDocumentsSearchClosePit, OutdatedDocumentsSearchOpenPit, OutdatedDocumentsSearchRead, - OutdatedDocumentsSearchClosePit, OutdatedDocumentsTransform, - MarkVersionIndexReady, - BaseState, - CreateReindexTempState, - MarkVersionIndexReadyConflict, - CreateNewTargetState, - CloneTempToSource, + PostInitState, + PrepareCompatibleMigration, + RefreshTarget, + ReindexSourceToTempClosePit, + ReindexSourceToTempIndexBulk, + ReindexSourceToTempOpenPit, + ReindexSourceToTempRead, + ReindexSourceToTempTransform, + SetSourceWriteBlockState, SetTempWriteBlock, - WaitForYellowSourceState, + State, TransformedDocumentsBulkIndex, - ReindexSourceToTempIndexBulk, - CheckUnknownDocumentsState, - CalculateExcludeFiltersState, - PostInitState, - CheckVersionIndexReadyActions, UpdateTargetMappingsMeta, - CheckTargetMappingsState, - PrepareCompatibleMigration, + UpdateTargetMappingsState, + UpdateTargetMappingsWaitForTaskState, + WaitForYellowSourceState, } from '../state'; import { type TransformErrorObjects, TransformSavedObjectDocumentError } from '../core'; import type { AliasAction, RetryableEsClientError } from '../actions'; import type { ResponseType } from '../next'; import { createInitialProgress } from './progress'; import { model } from './model'; +import type { BulkIndexOperationTuple, BulkOperation } from './create_batches'; describe('migrations v2 model', () => { const indexMapping: IndexMapping = { @@ -112,6 +116,26 @@ describe('migrations v2 model', () => { waitForMigrationCompletion: false, }; + const aProcessedDoc = { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }; + + const processedDocs: SavedObjectsRawDoc[] = [aProcessedDoc]; + + const bulkOperationBatches: BulkOperation[][] = [ + [ + [ + { + index: { + _id: aProcessedDoc._id, + }, + }, + aProcessedDoc._source, + ], + ], + ]; + describe('exponential retry delays for retryable_es_client_error', () => { let state: State = { ...baseState, @@ -1235,16 +1259,7 @@ describe('migrations v2 model', () => { }); describe('and mappings match (diffMappings == false)', () => { - const unchangedMappingsState: State = { - ...waitForYellowSourceState, - controlState: 'WAIT_FOR_YELLOW_SOURCE', - kibanaVersion: '7.12.0', // new version! - currentAlias: '.kibana', - versionAlias: '.kibana_7.12.0', - versionIndex: '.kibana_7.11.0_001', - }; - - test('WAIT_FOR_YELLOW_SOURCE -> PREPARE_COMPATIBLE_MIGRATION', () => { + test('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED', () => { const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({ '.kibana_7.11.0_001': { aliases: { @@ -1255,44 +1270,11 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(unchangedMappingsState, res) as PrepareCompatibleMigration; + const newState = model(waitForYellowSourceState, res) as CleanupUnknownAndExcluded; - expect(newState.controlState).toEqual('PREPARE_COMPATIBLE_MIGRATION'); - expect(newState.targetIndexRawMappings).toEqual({ - _meta: { - migrationMappingPropertyHashes: { - new_saved_object_type: '4a11183eee21e6fbad864f7a30b39ad0', - }, - }, - properties: { - new_saved_object_type: { - properties: { - value: { - type: 'text', - }, - }, - }, - }, - }); - expect(newState.versionAlias).toEqual('.kibana_7.12.0'); - expect(newState.currentAlias).toEqual('.kibana'); - // will point to - expect(newState.targetIndex).toEqual('.kibana_7.11.0_001'); - expect(newState.preTransformDocsActions).toEqual([ - { - add: { - alias: '.kibana_7.12.0', - index: '.kibana_7.11.0_001', - }, - }, - { - remove: { - alias: '.kibana_7.11.0', - index: '.kibana_7.11.0_001', - must_exist: true, - }, - }, - ]); + expect(newState.controlState).toEqual('CLEANUP_UNKNOWN_AND_EXCLUDED'); + expect(newState.targetIndex).toEqual(baseState.versionIndex); + expect(newState.versionIndexReadyActions).toEqual(Option.none); }); }); @@ -1312,23 +1294,17 @@ describe('migrations v2 model', () => { }, }; - const changedMappingsState: State = { + const changedMappingsState: WaitForYellowSourceState = { ...waitForYellowSourceState, - controlState: 'WAIT_FOR_YELLOW_SOURCE', - kibanaVersion: '7.12.0', // new version! - currentAlias: '.kibana', - versionAlias: '.kibana_7.12.0', - versionIndex: '.kibana_7.11.0_001', 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, }); @@ -1354,6 +1330,221 @@ 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, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + sourceIndex: Option.some('.kibana_7.11.0_001') as Option.Some, + sourceIndexMappings: baseState.targetIndexMappings, + targetIndex: baseState.versionIndex, + kibanaVersion: '7.12.0', // new version! + currentAlias: '.kibana', + versionAlias: '.kibana_7.12.0', + aliases: { + '.kibana': '.kibana_7.11.0_001', + '.kibana_7.11.0': '.kibana_7.11.0_001', + }, + versionIndexReadyActions: Option.none, + }; + + describe('if action succeeds', () => { + test('CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK', () => { + const res: ResponseType<'CLEANUP_UNKNOWN_AND_EXCLUDED'> = Either.right({ + type: 'cleanup_started' as const, + taskId: '1234', + unknownDocs: [], + errorsByType: {}, + }); + const newState = model(cleanupUnknownAndExcluded, res) as PrepareCompatibleMigration; + + expect(newState.controlState).toEqual('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'); + // expect(newState.targetIndexRawMappings).toEqual(indexMapping); + // expect(newState.targetIndexMappings).toEqual(indexMapping); + // expect(newState.targetIndex).toEqual('.kibana_7.11.0_001'); + // expect(newState.preTransformDocsActions).toEqual([ + // { + // add: { + // alias: '.kibana_7.12.0', + // index: '.kibana_7.11.0_001', + // }, + // }, + // { + // remove: { + // alias: '.kibana_7.11.0', + // index: '.kibana_7.11.0_001', + // must_exist: true, + // }, + // }, + // ]); + }); + }); + + test('CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL if discardUnknownObjects=false', () => { + const res: ResponseType<'CLEANUP_UNKNOWN_AND_EXCLUDED'> = Either.left({ + type: 'unknown_docs_found' as const, + unknownDocs: [ + { id: 'dashboard:12', type: 'dashboard' }, + { id: 'foo:17', type: 'foo' }, + ], + }); + + const newState = model(cleanupUnknownAndExcluded, res); + + expect(newState).toMatchObject({ + controlState: 'FATAL', + reason: expect.stringContaining( + 'Migration failed because some documents were found which use unknown saved object types' + ), + }); + }); + }); + + describe('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK', () => { + const cleanupUnknownAndExcludedWaitForTask: CleanupUnknownAndExcludedWaitForTaskState = { + ...baseState, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK', + deleteByQueryTaskId: '1234', + sourceIndex: Option.some('.kibana_7.11.0_001') as Option.Some, + sourceIndexMappings: baseState.targetIndexMappings, + targetIndex: baseState.versionIndex, + kibanaVersion: '7.12.0', // new version! + currentAlias: '.kibana', + versionAlias: '.kibana_7.12.0', + aliases: { + '.kibana': '.kibana_7.11.0_001', + '.kibana_7.11.0': '.kibana_7.11.0_001', + }, + versionIndexReadyActions: Option.none, + }; + + test('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK when response is left wait_for_task_completion_timeout', () => { + const res: ResponseType<'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK'> = Either.left({ + message: '[timeout_exception] Timeout waiting for ...', + type: 'wait_for_task_completion_timeout', + }); + const newState = model(cleanupUnknownAndExcludedWaitForTask, res); + expect(newState.controlState).toEqual('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + }); + + test('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION if action succeeds', () => { + const res: ResponseType<'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'> = Either.right({ + type: 'cleanup_successful' as const, + }); + const newState = model( + cleanupUnknownAndExcludedWaitForTask, + res + ) as PrepareCompatibleMigration; + + expect(newState.controlState).toEqual('PREPARE_COMPATIBLE_MIGRATION'); + expect(newState.targetIndexRawMappings).toEqual(indexMapping); + expect(newState.targetIndexMappings).toEqual(indexMapping); + expect(newState.targetIndex).toEqual('.kibana_7.11.0_001'); + expect(newState.preTransformDocsActions).toEqual([ + { + add: { + alias: '.kibana_7.12.0', + index: '.kibana_7.11.0_001', + }, + }, + { + remove: { + alias: '.kibana_7.11.0', + index: '.kibana_7.11.0_001', + must_exist: true, + }, + }, + ]); + }); + + test('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> CLEANUP_UNKNOWN_AND_EXCLUDED if the deleteQuery fails and we have some attempts left', () => { + const res: ResponseType<'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'> = Either.left({ + type: 'cleanup_failed' as const, + failures: ['Failed to delete dashboard:12345', 'Failed to delete dashboard:67890'], + versionConflicts: 12, + }); + + const newState = model(cleanupUnknownAndExcludedWaitForTask, res); + + expect(newState).toMatchObject({ + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + logs: [ + { + level: 'warning', + message: + 'Errors occurred whilst deleting unwanted documents. Another instance is probably updating or deleting documents in the same index. Retrying attempt 1.', + }, + ], + }); + }); + + test('CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> FAIL if the deleteQuery fails after N retries', () => { + const res: ResponseType<'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'> = Either.left({ + type: 'cleanup_failed' as const, + failures: ['Failed to delete dashboard:12345', 'Failed to delete dashboard:67890'], + }); + + const newState = model( + { + ...cleanupUnknownAndExcludedWaitForTask, + retryCount: cleanupUnknownAndExcludedWaitForTask.retryAttempts, + }, + res + ); + + expect(newState).toMatchObject({ + controlState: 'FATAL', + reason: expect.stringContaining( + 'Migration failed because it was unable to delete unwanted documents from the .kibana_7.11.0_001 system index' + ), + }); + }); + }); + describe('CHECK_UNKNOWN_DOCUMENTS', () => { const mappingsWithUnknownType = { properties: { @@ -1536,7 +1727,7 @@ describe('migrations v2 model', () => { }); it('CALCULATE_EXCLUDE_FILTERS -> CREATE_REINDEX_TEMP if action succeeds with filters', () => { const res: ResponseType<'CALCULATE_EXCLUDE_FILTERS'> = Either.right({ - mustNotClauses: [{ term: { fieldA: 'abc' } }], + filterClauses: [{ term: { fieldA: 'abc' } }], errorsByType: { type1: new Error('an error!') }, }); const newState = model(state, res); @@ -1784,12 +1975,6 @@ describe('migrations v2 model', () => { transformErrors: [], progress: { processed: undefined, total: 1 }, }; - const processedDocs = [ - { - _id: 'a:b', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, - }, - ] as SavedObjectsRawDoc[]; it('REINDEX_SOURCE_TO_TEMP_TRANSFORM -> REINDEX_SOURCE_TO_TEMP_INDEX_BULK if action succeeded', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_TRANSFORM'> = Either.right({ @@ -1798,7 +1983,7 @@ describe('migrations v2 model', () => { const newState = model(state, res) as ReindexSourceToTempIndexBulk; expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_INDEX_BULK'); expect(newState.currentBatch).toEqual(0); - expect(newState.transformedDocBatches).toEqual([processedDocs]); + expect(newState.bulkOperationBatches).toEqual(bulkOperationBatches); expect(newState.progress.processed).toBe(0); // Result of `(undefined ?? 0) + corruptDocumentsId.length` }); @@ -1854,18 +2039,10 @@ describe('migrations v2 model', () => { }); describe('REINDEX_SOURCE_TO_TEMP_INDEX_BULK', () => { - const transformedDocBatches = [ - [ - { - _id: 'a:b', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, - }, - ], - ] as [SavedObjectsRawDoc[]]; const reindexSourceToTempIndexBulkState: ReindexSourceToTempIndexBulk = { ...baseState, controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', - transformedDocBatches, + bulkOperationBatches, currentBatch: 0, versionIndexReadyActions: Option.none, sourceIndex: Option.some('.kibana') as Option.Some, @@ -2024,6 +2201,18 @@ describe('migrations v2 model', () => { preTransformDocsActions: [someAliasAction], }; + it('PREPARE_COMPATIBLE_MIGRATIONS -> REFRESH_TARGET if action succeeds and we must refresh the index', () => { + const res: ResponseType<'PREPARE_COMPATIBLE_MIGRATION'> = Either.right( + 'update_aliases_succeeded' + ); + const newState = model( + { ...state, mustRefresh: true }, + res + ) as OutdatedDocumentsSearchOpenPit; + expect(newState.controlState).toEqual('REFRESH_TARGET'); + expect(newState.versionIndexReadyActions).toEqual(Option.none); + }); + it('PREPARE_COMPATIBLE_MIGRATIONS -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if action succeeds', () => { const res: ResponseType<'PREPARE_COMPATIBLE_MIGRATION'> = Either.right( 'update_aliases_succeeded' @@ -2033,6 +2222,19 @@ describe('migrations v2 model', () => { expect(newState.versionIndexReadyActions).toEqual(Option.none); }); + it('PREPARE_COMPATIBLE_MIGRATIONS -> REFRESH_TARGET if action fails because the alias is not found', () => { + const res: ResponseType<'PREPARE_COMPATIBLE_MIGRATION'> = Either.left({ + type: 'alias_not_found_exception', + }); + + const newState = model( + { ...state, mustRefresh: true }, + res + ) as OutdatedDocumentsSearchOpenPit; + expect(newState.controlState).toEqual('REFRESH_TARGET'); + expect(newState.versionIndexReadyActions).toEqual(Option.none); + }); + it('PREPARE_COMPATIBLE_MIGRATIONS -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if action fails because the alias is not found', () => { const res: ResponseType<'PREPARE_COMPATIBLE_MIGRATION'> = Either.left({ type: 'alias_not_found_exception', @@ -2268,12 +2470,6 @@ describe('migrations v2 model', () => { progress: createInitialProgress(), }; describe('OUTDATED_DOCUMENTS_TRANSFORM if action succeeds', () => { - const processedDocs = [ - { - _id: 'a:b', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, - }, - ] as SavedObjectsRawDoc[]; test('OUTDATED_DOCUMENTS_TRANSFORM -> TRANSFORMED_DOCUMENTS_BULK_INDEX if action succeeds', () => { const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right({ processedDocs }); const newState = model( @@ -2281,7 +2477,7 @@ describe('migrations v2 model', () => { res ) as TransformedDocumentsBulkIndex; expect(newState.controlState).toEqual('TRANSFORMED_DOCUMENTS_BULK_INDEX'); - expect(newState.transformedDocBatches).toEqual([processedDocs]); + expect(newState.bulkOperationBatches).toEqual(bulkOperationBatches); expect(newState.currentBatch).toEqual(0); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); @@ -2367,30 +2563,28 @@ describe('migrations v2 model', () => { }); describe('TRANSFORMED_DOCUMENTS_BULK_INDEX', () => { - const transformedDocBatches = [ - [ - // batch 0 - { - _id: 'a:b', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, - }, - { - _id: 'a:c', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, - }, - ], - [ - // batch 1 - { - _id: 'a:d', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + const idToIndexOperation = (_id: string): BulkIndexOperationTuple => [ + // "index" operations have a first part with the operation and the SO id + { + index: { + _id, }, - ], - ] as SavedObjectsRawDoc[][]; + }, + // and a second part with the object _source + { type: 'a', a: { name: `HOI ${_id}!` }, migrationVersion: {}, references: [] }, + // these two parts are then serialized to NDJSON by esClient and sent over with POST _bulk + ]; + + const customBulkOperationBatches: BulkOperation[][] = [ + // batch 0 + ['a:b', 'a:c'].map(idToIndexOperation), + // batch 1 + ['a:d'].map(idToIndexOperation), + ]; const transformedDocumentsBulkIndexState: TransformedDocumentsBulkIndex = { ...baseState, controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX', - transformedDocBatches, + bulkOperationBatches: customBulkOperationBatches, currentBatch: 0, versionIndexReadyActions: Option.none, sourceIndex: Option.some('.kibana') as Option.Some, @@ -2542,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 167a5d3771b78..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 @@ -9,7 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; -import { type AliasAction, isTypeof } from '../actions'; +import { isTypeof } from '../actions'; +import type { AliasAction } from '../actions'; import type { AllActionStates, State } from '../state'; import type { ResponseType } from '../next'; import { @@ -278,30 +279,6 @@ export const model = (currentState: State, resW: ResponseType): ], }; } - } else if (stateP.controlState === 'PREPARE_COMPATIBLE_MIGRATION') { - const res = resW as ExcludeRetryableEsError>; - if (Either.isRight(res)) { - return { - ...stateP, - controlState: 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', - }; - } else if (Either.isLeft(res)) { - // Note: if multiple newer Kibana versions are competing with each other to perform a migration, - // it might happen that another Kibana instance has deleted this instance's version index. - // NIT to handle this in properly, we'd have to add a PREPARE_COMPATIBLE_MIGRATION_CONFLICT step, - // similar to MARK_VERSION_INDEX_READY_CONFLICT. - if (isTypeof(res.left, 'alias_not_found_exception')) { - // We assume that the alias was already deleted by another Kibana instance - return { - ...stateP, - controlState: 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', - }; - } else { - throwBadResponse(stateP, res.left as never); - } - } else { - throwBadResponse(stateP, res); - } } else if (stateP.controlState === 'LEGACY_SET_WRITE_BLOCK') { const res = resW as ExcludeRetryableEsError>; // If the write block is successfully in place @@ -447,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) && @@ -459,36 +435,17 @@ export const model = (currentState: State, resW: ResponseType): stateP.targetIndexMappings ) ) { - // The source index .kibana is pointing to. E.g: ".xx8.7.0_001" - const source = stateP.sourceIndex.value; - + // the existing mappings match, we can avoid reindexing return { ...stateP, - controlState: 'PREPARE_COMPATIBLE_MIGRATION', - sourceIndex: Option.none, - targetIndex: source!, - targetIndexRawMappings: stateP.sourceIndexMappings, - targetIndexMappings: mergeMigrationMappingPropertyHashes( - stateP.targetIndexMappings, - stateP.sourceIndexMappings - ), - preTransformDocsActions: [ - // Point the version alias to the source index. This let's other Kibana - // instances know that a migration for the current version is "done" - // even though we may be waiting for document transformations to finish. - { add: { index: source!, alias: stateP.versionAlias } }, - ...buildRemoveAliasActions(source!, Object.keys(stateP.aliases), [ - stateP.currentAlias, - stateP.versionAlias, - ]), - ], + 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 { - // the mappings have changed, but changes might still be compatible return { ...stateP, - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'UPDATE_SOURCE_MAPPINGS', }; } } else if (Either.isLeft(res)) { @@ -507,6 +464,155 @@ 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)) { + if (res.right.unknownDocs.length) { + logs = [ + ...stateP.logs, + { level: 'warning', message: extractDiscardedUnknownDocs(res.right.unknownDocs) }, + ]; + } + + logs = [ + ...logs, + ...Object.entries(res.right.errorsByType).map(([soType, error]) => ({ + level: 'warning' as const, + message: `Ignored excludeOnUpgrade hook on type [${soType}] that failed with error: "${error.toString()}"`, + })), + ]; + + return { + ...stateP, + logs, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK', + deleteByQueryTaskId: res.right.taskId, + }; + } else { + return { + ...stateP, + controlState: 'FATAL', + reason: extractUnknownDocFailureReason( + stateP.migrationDocLinks.resolveMigrationFailures, + res.left.unknownDocs + ), + }; + } + } else if (stateP.controlState === 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + const source = stateP.sourceIndex.value; + return { + ...stateP, + logs, + controlState: 'PREPARE_COMPATIBLE_MIGRATION', + mustRefresh: + stateP.mustRefresh || typeof res.right.deleted === 'undefined' || res.right.deleted > 0, + targetIndexRawMappings: stateP.sourceIndexMappings, + targetIndexMappings: mergeMigrationMappingPropertyHashes( + stateP.targetIndexMappings, + stateP.sourceIndexMappings + ), + preTransformDocsActions: [ + // Point the version alias to the source index. This let's other Kibana + // instances know that a migration for the current version is "done" + // even though we may be waiting for document transformations to finish. + { add: { index: source!, alias: stateP.versionAlias } }, + ...buildRemoveAliasActions(source!, Object.keys(stateP.aliases), [ + stateP.currentAlias, + stateP.versionAlias, + ]), + ], + }; + } else { + if (isTypeof(res.left, 'wait_for_task_completion_timeout')) { + // After waiting for the specified timeout, the task has not yet + // completed. Retry this step to see if the task has completed after an + // exponential delay. We will basically keep polling forever until the + // Elasticsearch task succeeds or fails. + return delayRetryState(stateP, res.left.message, Number.MAX_SAFE_INTEGER); + } else { + if (stateP.retryCount < stateP.retryAttempts) { + const retryCount = stateP.retryCount + 1; + const retryDelay = 1500 + 1000 * Math.random(); + return { + ...stateP, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + mustRefresh: true, + retryCount, + retryDelay, + logs: [ + ...stateP.logs, + { + level: 'warning', + message: `Errors occurred whilst deleting unwanted documents. Another instance is probably updating or deleting documents in the same index. Retrying attempt ${retryCount}.`, + }, + ], + }; + } else { + const failures = res.left.failures.length; + const versionConflicts = res.left.versionConflicts ?? 0; + + let reason = `Migration failed because it was unable to delete unwanted documents from the ${stateP.sourceIndex.value} system index (${failures} failures and ${versionConflicts} conflicts)`; + if (failures) { + reason += `:\n` + res.left.failures.map((failure: string) => `- ${failure}\n`).join(''); + } + return { + ...stateP, + controlState: 'FATAL', + reason, + }; + } + } + } + } else if (stateP.controlState === 'PREPARE_COMPATIBLE_MIGRATION') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: stateP.mustRefresh ? 'REFRESH_TARGET' : 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', + }; + } else if (Either.isLeft(res)) { + // Note: if multiple newer Kibana versions are competing with each other to perform a migration, + // it might happen that another Kibana instance has deleted this instance's version index. + // NIT to handle this in properly, we'd have to add a PREPARE_COMPATIBLE_MIGRATION_CONFLICT step, + // similar to MARK_VERSION_INDEX_READY_CONFLICT. + if (isTypeof(res.left, 'alias_not_found_exception')) { + // We assume that the alias was already deleted by another Kibana instance + return { + ...stateP, + controlState: stateP.mustRefresh + ? 'REFRESH_TARGET' + : 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', + }; + } else { + throwBadResponse(stateP, res.left as never); + } + } else { + throwBadResponse(stateP, res); + } } else if (stateP.controlState === 'CHECK_UNKNOWN_DOCUMENTS') { const res = resW as ExcludeRetryableEsError>; @@ -579,7 +685,7 @@ export const model = (currentState: State, resW: ResponseType): if (Either.isRight(res)) { excludeOnUpgradeQuery = addMustNotClausesToBoolQuery( - res.right.mustNotClauses, + res.right.filterClauses, stateP.excludeOnUpgradeQuery?.bool ); @@ -733,10 +839,8 @@ export const model = (currentState: State, resW: ResponseType): (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) || stateP.discardCorruptObjects ) { - const processedDocs = Either.isRight(res) - ? res.right.processedDocs - : res.left.processedDocs; - const batches = createBatches(processedDocs, stateP.tempIndex, stateP.maxBatchSizeBytes); + const documents = Either.isRight(res) ? res.right.processedDocs : res.left.processedDocs; + const batches = createBatches({ documents, maxBatchSizeBytes: stateP.maxBatchSizeBytes }); if (Either.isRight(batches)) { let corruptDocumentIds = stateP.corruptDocumentIds; let transformErrors = stateP.transformErrors; @@ -751,7 +855,7 @@ export const model = (currentState: State, resW: ResponseType): corruptDocumentIds, transformErrors, controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', // handles the actual bulk indexing into temp index - transformedDocBatches: batches.right, + bulkOperationBatches: batches.right, currentBatch: 0, progress, }; @@ -760,7 +864,7 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, controlState: 'FATAL', reason: fatalReasonDocumentExceedsMaxBatchSizeBytes({ - _id: batches.left.document._id, + _id: batches.left.documentId, docSizeBytes: batches.left.docSizeBytes, maxBatchSizeBytes: batches.left.maxBatchSizeBytes, }), @@ -796,7 +900,7 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - if (stateP.currentBatch + 1 < stateP.transformedDocBatches.length) { + if (stateP.currentBatch + 1 < stateP.bulkOperationBatches.length) { return { ...stateP, controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', @@ -938,25 +1042,40 @@ export const model = (currentState: State, resW: ResponseType): } else { // we don't have any more outdated documents and need to either fail or move on to updating the target mappings. if (stateP.corruptDocumentIds.length > 0 || stateP.transformErrors.length > 0) { - const transformFailureReason = extractTransformFailuresReason( - stateP.migrationDocLinks.resolveMigrationFailures, - stateP.corruptDocumentIds, - stateP.transformErrors - ); - return { - ...stateP, - controlState: 'FATAL', - reason: transformFailureReason, - }; - } else { - // If there are no more results we have transformed all outdated - // documents and we didn't encounter any corrupt documents or transformation errors - // and can proceed to the next step - return { - ...stateP, - controlState: 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', - }; + if (!stateP.discardCorruptObjects) { + const transformFailureReason = extractTransformFailuresReason( + stateP.migrationDocLinks.resolveMigrationFailures, + stateP.corruptDocumentIds, + stateP.transformErrors + ); + return { + ...stateP, + controlState: 'FATAL', + reason: transformFailureReason, + }; + } + + // at this point, users have configured kibana to discard corrupt objects + // thus, we can ignore corrupt documents and transform errors and proceed with the migration + logs = [ + ...stateP.logs, + { + level: 'warning', + message: extractDiscardedCorruptDocs( + stateP.corruptDocumentIds, + stateP.transformErrors + ), + }, + ]; } + + // If there are no more results we have transformed all outdated + // documents and we didn't encounter any corrupt documents or transformation errors + // and can proceed to the next step + return { + ...stateP, + controlState: 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', + }; } } else { throwBadResponse(stateP, res); @@ -968,20 +1087,36 @@ export const model = (currentState: State, resW: ResponseType): // Otherwise the progress might look off when there are errors. const progress = incrementProcessedProgress(stateP.progress, stateP.outdatedDocuments.length); - if (Either.isRight(res)) { - // we haven't seen corrupt documents or any transformation errors thus far in the migration - // index the migrated docs - if (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) { - const batches = createBatches( - res.right.processedDocs, - stateP.targetIndex, - stateP.maxBatchSizeBytes - ); + if ( + Either.isRight(res) || + (isTypeof(res.left, 'documents_transform_failed') && stateP.discardCorruptObjects) + ) { + // we might have some transformation errors, but user has chosen to discard them + if ( + (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) || + stateP.discardCorruptObjects + ) { + const documents = Either.isRight(res) ? res.right.processedDocs : res.left.processedDocs; + + let corruptDocumentIds = stateP.corruptDocumentIds; + let transformErrors = stateP.transformErrors; + + if (Either.isLeft(res)) { + corruptDocumentIds = [...stateP.corruptDocumentIds, ...res.left.corruptDocumentIds]; + transformErrors = [...stateP.transformErrors, ...res.left.transformErrors]; + } + + const batches = createBatches({ + documents, + corruptDocumentIds, + transformErrors, + maxBatchSizeBytes: stateP.maxBatchSizeBytes, + }); if (Either.isRight(batches)) { return { ...stateP, controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX', - transformedDocBatches: batches.right, + bulkOperationBatches: batches.right, currentBatch: 0, hasTransformedDocs: true, progress, @@ -991,7 +1126,7 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, controlState: 'FATAL', reason: fatalReasonDocumentExceedsMaxBatchSizeBytes({ - _id: batches.left.document._id, + _id: batches.left.documentId, docSizeBytes: batches.left.docSizeBytes, maxBatchSizeBytes: batches.left.maxBatchSizeBytes, }), @@ -1024,7 +1159,7 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'TRANSFORMED_DOCUMENTS_BULK_INDEX') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - if (stateP.currentBatch + 1 < stateP.transformedDocBatches.length) { + if (stateP.currentBatch + 1 < stateP.bulkOperationBatches.length) { return { ...stateP, controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/retry_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/retry_state.ts index 532cc70347916..f6fb8bf8a1904 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/retry_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/retry_state.ts @@ -6,9 +6,16 @@ * Side Public License, v 1. */ -import { State } from '../state'; +import type { MigrationLog } from '../types'; -export const delayRetryState = ( +export interface RetryableState { + controlState: string; + retryCount: number; + retryDelay: number; + logs: MigrationLog[]; +} + +export const delayRetryState = ( state: S, errorMessage: string, /** How many times to retry a step that fails */ @@ -39,7 +46,7 @@ export const delayRetryState = ( }; } }; -export const resetRetryState = (state: S): S => { +export const resetRetryState = (state: S): S => { return { ...state, retryCount: 0, 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 386786baf60c8..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,45 +7,50 @@ */ 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, + WaitForYellowSourceState, } from './state'; import type { TransformRawDocs } from './types'; import * as Actions from './actions'; +import { REMOVED_TYPES } from './core'; type ActionMap = ReturnType; @@ -63,12 +68,36 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra return { INIT: (state: InitState) => Actions.initAction({ client, indices: [state.currentAlias, state.versionAlias] }), - PREPARE_COMPATIBLE_MIGRATION: (state: PrepareCompatibleMigration) => - Actions.updateAliases({ client, aliasActions: state.preTransformDocsActions }), WAIT_FOR_MIGRATION_COMPLETION: (state: WaitForMigrationCompletionState) => 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, + indexName: state.sourceIndex.value, + discardUnknownDocs: state.discardUnknownObjects, + excludeOnUpgradeQuery: state.excludeOnUpgradeQuery, + excludeFromUpgradeFilterHooks: state.excludeFromUpgradeFilterHooks, + knownTypes: state.knownTypes, + removedTypes: REMOVED_TYPES, + }), + CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK: ( + state: CleanupUnknownAndExcludedWaitForTaskState + ) => + Actions.waitForDeleteByQueryTask({ + client, + taskId: state.deleteByQueryTaskId, + timeout: '120s', + }), + PREPARE_COMPATIBLE_MIGRATION: (state: PrepareCompatibleMigration) => + Actions.updateAliases({ client, aliasActions: state.preTransformDocsActions }), CHECK_UNKNOWN_DOCUMENTS: (state: CheckUnknownDocumentsState) => Actions.checkForUnknownDocs({ client, @@ -117,7 +146,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.bulkOverwriteTransformedDocuments({ client, index: state.tempIndex, - transformedDocs: state.transformedDocBatches[state.currentBatch], + operations: state.bulkOperationBatches[state.currentBatch], /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. @@ -142,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({ @@ -151,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) => @@ -178,14 +207,14 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.bulkOverwriteTransformedDocuments({ client, index: state.targetIndex, - transformedDocs: state.transformedDocBatches[state.currentBatch], + operations: state.bulkOperationBatches[state.currentBatch], /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. * Although any further step must run "refresh" for the target index - * before we reach out to the MARK_VERSION_INDEX_READY step. * Right now, it's performed during OUTDATED_DOCUMENTS_REFRESH step. */ + refresh: false, }), MARK_VERSION_INDEX_READY: (state: MarkVersionIndexReady) => Actions.updateAliases({ client, aliasActions: state.versionIndexReadyActions.value }), 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 f3a59fadf2dd4..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 @@ -18,6 +18,7 @@ import type { ControlState } from './state_action_machine'; import type { AliasAction } from './actions'; import type { TransformErrorObjects } from './core'; import type { MigrationLog, Progress } from './types'; +import type { BulkOperation } from './model/create_batches'; export interface BaseState extends ControlState { /** The first part of the index name such as `.kibana` or `.kibana_task_manager` */ @@ -180,14 +181,37 @@ export interface PostInitState extends BaseState { */ readonly targetIndexRawMappings?: IndexMapping; readonly versionIndexReadyActions: Option.Option; - readonly outdatedDocumentsQuery: QueryDslQueryContainer; } +export interface SourceExistsState { + readonly sourceIndex: Option.Some; +} +export type BaseWithSource = BaseState & SourceExistsState; +export type PostInitWithSource = PostInitState & SourceExistsState; + export interface DoneState extends PostInitState { /** Migration completed successfully */ readonly controlState: 'DONE'; } +export interface CleanupUnknownAndExcluded extends PostInitWithSource { + /** Clean the source index, removing SOs with unknown and excluded types */ + readonly controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED'; + readonly sourceIndexMappings: IndexMapping; + readonly aliases: Record; + /** The cleanup operation has deleted one or more documents, we gotta refresh the index */ + readonly mustRefresh?: boolean; +} + +export interface CleanupUnknownAndExcludedWaitForTaskState extends PostInitWithSource { + readonly controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'; + readonly deleteByQueryTaskId: string; + readonly sourceIndexMappings: IndexMapping; + readonly aliases: Record; + /** The cleanup operation has deleted one or more documents, we gotta refresh the index */ + readonly mustRefresh?: boolean; +} + /** * Compatibe migrations do not require migrating to a new index because all * schema changes are compatible with current index mappings. @@ -196,11 +220,13 @@ export interface DoneState extends PostInitState { * need to make sure that no older Kibana versions are still writing to target * index. */ -export interface PrepareCompatibleMigration extends PostInitState { +export interface PrepareCompatibleMigration extends PostInitWithSource { /** We have found a schema-compatible migration, this means we can optimise our migration steps */ readonly controlState: 'PREPARE_COMPATIBLE_MIGRATION'; /** Alias-level actions that prepare for this migration */ readonly preTransformDocsActions: AliasAction[]; + /** Indicates whether we must refresh the index */ + readonly mustRefresh?: boolean; } export interface FatalState extends BaseState { @@ -210,30 +236,33 @@ export interface FatalState extends BaseState { readonly reason: string; } -export interface WaitForYellowSourceState extends BaseState { +export interface WaitForYellowSourceState extends BaseWithSource { /** Wait for the source index to be yellow before reading from it. */ readonly controlState: 'WAIT_FOR_YELLOW_SOURCE'; + readonly sourceIndexMappings: IndexMapping; + 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 BaseState { +export interface CheckUnknownDocumentsState extends BaseWithSource { /** Check if any unknown document is present in the source index */ readonly controlState: 'CHECK_UNKNOWN_DOCUMENTS'; - readonly sourceIndex: Option.Some; readonly sourceIndexMappings: IndexMapping; } -export interface SetSourceWriteBlockState extends PostInitState { +export interface SetSourceWriteBlockState extends PostInitWithSource { /** Set a write block on the source index to prevent any further writes */ readonly controlState: 'SET_SOURCE_WRITE_BLOCK'; - readonly sourceIndex: Option.Some; } -export interface CalculateExcludeFiltersState extends PostInitState { +export interface CalculateExcludeFiltersState extends PostInitWithSource { readonly controlState: 'CALCULATE_EXCLUDE_FILTERS'; - readonly sourceIndex: Option.Some; } export interface CreateNewTargetState extends PostInitState { @@ -243,19 +272,17 @@ export interface CreateNewTargetState extends PostInitState { readonly versionIndexReadyActions: Option.Some; } -export interface CreateReindexTempState extends PostInitState { +export interface CreateReindexTempState extends PostInitWithSource { /** * Create a target index with mappings from the source index and registered * plugins */ readonly controlState: 'CREATE_REINDEX_TEMP'; - readonly sourceIndex: Option.Some; } -export interface ReindexSourceToTempOpenPit extends PostInitState { +export interface ReindexSourceToTempOpenPit extends PostInitWithSource { /** Open PIT to the source index */ readonly controlState: 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT'; - readonly sourceIndex: Option.Some; } interface ReindexSourceToTempBatch extends PostInitState { @@ -282,24 +309,19 @@ export interface ReindexSourceToTempTransform extends ReindexSourceToTempBatch { export interface ReindexSourceToTempIndexBulk extends ReindexSourceToTempBatch { readonly controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'; - readonly transformedDocBatches: [SavedObjectsRawDoc[]]; + readonly bulkOperationBatches: BulkOperation[][]; readonly currentBatch: number; } -export type SetTempWriteBlock = PostInitState & { - /** - * - */ +export interface SetTempWriteBlock extends PostInitWithSource { readonly controlState: 'SET_TEMP_WRITE_BLOCK'; - readonly sourceIndex: Option.Some; -}; +} -export interface CloneTempToSource extends PostInitState { +export interface CloneTempToSource extends PostInitWithSource { /** * Clone the temporary reindex index into */ readonly controlState: 'CLONE_TEMP_TO_TARGET'; - readonly sourceIndex: Option.Some; } export interface RefreshTarget extends PostInitState { @@ -380,7 +402,7 @@ export interface TransformedDocumentsBulkIndex extends PostInitState { * Write the up-to-date transformed documents to the target index */ readonly controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX'; - readonly transformedDocBatches: SavedObjectsRawDoc[][]; + readonly bulkOperationBatches: BulkOperation[][]; readonly currentBatch: number; readonly lastHitSortValue: number[] | undefined; readonly hasTransformedDocs: boolean; @@ -423,8 +445,7 @@ export interface MarkVersionIndexReadyConflict extends PostInitState { * If we're migrating from a legacy index we need to perform some additional * steps to prepare this index so that it can be used as a migration 'source'. */ -export interface LegacyBaseState extends PostInitState { - readonly sourceIndex: Option.Some; +export interface LegacyBaseState extends PostInitWithSource { readonly legacyPreMigrationDoneActions: AliasAction[]; /** * The mappings read from the legacy index, used to create a new reindex @@ -474,9 +495,12 @@ export type State = Readonly< | FatalState | InitState | PrepareCompatibleMigration + | CleanupUnknownAndExcluded + | CleanupUnknownAndExcludedWaitForTaskState | WaitForMigrationCompletionState | DoneState | WaitForYellowSourceState + | UpdateSourceMappingsState | CheckUnknownDocumentsState | SetSourceWriteBlockState | CalculateExcludeFiltersState diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts new file mode 100644 index 0000000000000..92334d396adc0 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.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 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 { + IncompatibleClusterRoutingAllocation, + RetryableEsClientError, + WaitForTaskCompletionTimeout, + IndexNotFound, +} from '../../actions'; + +export { + initAction as init, + type InitActionParams, + type IncompatibleClusterRoutingAllocation, + type RetryableEsClientError, + type WaitForTaskCompletionTimeout, + type IndexNotFound, +} from '../../actions'; + +export interface ActionErrorTypeMap { + wait_for_task_completion_timeout: WaitForTaskCompletionTimeout; + incompatible_cluster_routing_allocation: IncompatibleClusterRoutingAllocation; + retryable_es_client_error: RetryableEsClientError; + index_not_found_exception: IndexNotFound; +} + +/** Type guard for narrowing the type of a left */ +export function isTypeof( + res: any, + typeString: T +): res is ActionErrorTypeMap[T] { + return res.type === typeString; +} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/create_context.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/create_context.ts new file mode 100644 index 0000000000000..cc4c7b63d3993 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/create_context.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 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 { MigratorContext } from './types'; +import type { MigrateIndexOptions } from '../migrate_index'; + +export type CreateContextOps = Omit; + +/** + * Create the context object that will be used for this index migration. + */ +export const createContext = ({ + types, + docLinks, + migrationConfig, + elasticsearchClient, + indexPrefix, + typeRegistry, + serializer, +}: CreateContextOps): MigratorContext => { + return { + indexPrefix, + types, + elasticsearchClient, + typeRegistry, + serializer, + maxRetryAttempts: migrationConfig.retryAttempts, + migrationDocLinks: docLinks.links.kibanaUpgradeSavedObjects, + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/index.ts new file mode 100644 index 0000000000000..9e7adc4c93868 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 type { MigratorContext } from './types'; +export { createContext, type CreateContextOps } from './create_context'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/types.ts new file mode 100644 index 0000000000000..2603a5b69a681 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/context/types.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 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { + ISavedObjectTypeRegistry, + ISavedObjectsSerializer, +} from '@kbn/core-saved-objects-server'; +import type { DocLinks } from '@kbn/doc-links'; + +/** + * The set of static, precomputed values and services used by the ZDT migration + */ +export interface MigratorContext { + /** The first part of the index name such as `.kibana` or `.kibana_task_manager` */ + readonly indexPrefix: string; + /** Name of the types that are living in the index */ + readonly types: string[]; + /** The client to use for communications with ES */ + readonly elasticsearchClient: ElasticsearchClient; + /** The maximum number of retries to attempt for a failing action */ + readonly maxRetryAttempts: number; + /** DocLinks for savedObjects. to reference online documentation */ + readonly migrationDocLinks: DocLinks['kibanaUpgradeSavedObjects']; + /** SO serializer to use for migration */ + readonly serializer: ISavedObjectsSerializer; + /** The SO type registry to use for the migration */ + readonly typeRegistry: ISavedObjectTypeRegistry; +} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/index.ts new file mode 100644 index 0000000000000..99e5c2d9cec44 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { runZeroDowntimeMigration, type RunZeroDowntimeMigrationOpts } from './run_zdt_migration'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migrate_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migrate_index.ts new file mode 100644 index 0000000000000..72ee369236e16 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migrate_index.ts @@ -0,0 +1,60 @@ +/* + * Copyright 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { + type SavedObjectsMigrationConfigType, + type MigrationResult, +} from '@kbn/core-saved-objects-base-server-internal'; +import type { + ISavedObjectTypeRegistry, + ISavedObjectsSerializer, +} from '@kbn/core-saved-objects-server'; +import type { Logger } from '@kbn/logging'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { migrationStateActionMachine } from './migration_state_action_machine'; +import type { VersionedTransformer } from '../document_migrator'; +import { createContext } from './context'; +import { next } from './next'; +import { model } from './model'; +import { createInitialState } from './state'; + +export interface MigrateIndexOptions { + indexPrefix: string; + types: string[]; + /** The SO type registry to use for the migration */ + typeRegistry: ISavedObjectTypeRegistry; + /** Logger to use for migration output */ + logger: Logger; + /** The document migrator to use to convert the document */ + documentMigrator: VersionedTransformer; + /** The migration config to use for the migration */ + migrationConfig: SavedObjectsMigrationConfigType; + /** docLinks contract to use to link to documentation */ + docLinks: DocLinksServiceStart; + /** SO serializer to use for migration */ + serializer: ISavedObjectsSerializer; + /** The client to use for communications with ES */ + elasticsearchClient: ElasticsearchClient; +} + +export const migrateIndex = async ({ + logger, + ...options +}: MigrateIndexOptions): Promise => { + const context = createContext(options); + const initialState = createInitialState(context); + + return migrationStateActionMachine({ + initialState, + next: next(context), + model, + context, + logger, + }); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migration_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migration_state_action_machine.ts new file mode 100644 index 0000000000000..8982a1a9c6c7e --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/migration_state_action_machine.ts @@ -0,0 +1,150 @@ +/* + * Copyright 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 { errors as EsErrors } from '@elastic/elasticsearch'; +import type { Logger } from '@kbn/logging'; +import { + getErrorMessage, + getRequestDebugMeta, +} from '@kbn/core-elasticsearch-client-server-internal'; +import { logStateTransition, logActionResponse } from '../common/utils'; +import { type Next, stateActionMachine } from '../state_action_machine'; +import { cleanup } from '../migrations_state_machine_cleanup'; +import type { State } from './state'; +import type { MigratorContext } from './context'; + +/** + * A specialized migrations-specific state-action machine that: + * - logs messages in state.logs + * - logs state transitions + * - logs action responses + * - resolves if the final state is DONE + * - rejects if the final state is FATAL + * - catches and logs exceptions and then rejects with a migrations specific error + */ +export async function migrationStateActionMachine({ + initialState, + context, + next, + model, + logger, +}: { + initialState: State; + context: MigratorContext; + next: Next; + model: (state: State, res: any, context: MigratorContext) => State; + logger: Logger; +}) { + const startTime = Date.now(); + // Since saved object index names usually start with a `.` and can be + // configured by users to include several `.`'s we can't use a logger tag to + // indicate which messages come from which index upgrade. + const logMessagePrefix = `[${context.indexPrefix}] `; + let prevTimestamp = startTime; + let lastState: State | undefined; + try { + const finalState = await stateActionMachine( + initialState, + (state) => next(state), + (state, res) => { + lastState = state; + logActionResponse(logger, logMessagePrefix, state, res); + const newState = model(state, res, context); + // Redact the state to reduce the memory consumption and so that we + // don't log sensitive information inside documents by only keeping + // the _id's of documents + const redactedNewState = { + ...newState, + /* TODO: commented until we have model stages that process outdated docs. (attrs not on model atm) + ...{ + outdatedDocuments: ( + (newState as ReindexSourceToTempTransform).outdatedDocuments ?? [] + ).map( + (doc) => + ({ + _id: doc._id, + } as SavedObjectsRawDoc) + ), + }, + ...{ + transformedDocBatches: ( + (newState as ReindexSourceToTempIndexBulk).transformedDocBatches ?? [] + ).map((batches) => batches.map((doc) => ({ _id: doc._id }))) as [SavedObjectsRawDoc[]], + }, + */ + }; + + const now = Date.now(); + logStateTransition( + logger, + logMessagePrefix, + state, + redactedNewState as State, + now - prevTimestamp + ); + prevTimestamp = now; + return newState; + } + ); + + const elapsedMs = Date.now() - startTime; + if (finalState.controlState === 'DONE') { + logger.info(logMessagePrefix + `Migration completed after ${Math.round(elapsedMs)}ms`); + return { + status: 'patched' as const, + destIndex: context.indexPrefix, + elapsedMs, + }; + } else if (finalState.controlState === 'FATAL') { + try { + await cleanup(context.elasticsearchClient, finalState); + } catch (e) { + logger.warn('Failed to cleanup after migrations:', e.message); + } + return Promise.reject( + new Error( + `Unable to complete saved object migrations for the [${context.indexPrefix}] index: ` + + finalState.reason + ) + ); + } else { + throw new Error('Invalid terminating control state'); + } + } catch (e) { + try { + await cleanup(context.elasticsearchClient, lastState); + } catch (err) { + logger.warn('Failed to cleanup after migrations:', err.message); + } + if (e instanceof EsErrors.ResponseError) { + // Log the failed request. This is very similar to the + // elasticsearch-service's debug logs, but we log everything in single + // line until we have sub-ms resolution in our cloud logs. Because this + // is error level logs, we're also more careful and don't log the request + // body since this can very likely have sensitive saved objects. + const req = getRequestDebugMeta(e.meta); + const failedRequestMessage = `Unexpected Elasticsearch ResponseError: statusCode: ${ + req.statusCode + }, method: ${req.method}, url: ${req.url} error: ${getErrorMessage(e)},`; + logger.error(logMessagePrefix + failedRequestMessage); + throw new Error( + `Unable to complete saved object migrations for the [${context.indexPrefix}] index. Please check the health of your Elasticsearch cluster and try again. ${failedRequestMessage}` + ); + } else { + logger.error(e); + + const newError = new Error( + `Unable to complete saved object migrations for the [${context.indexPrefix}] index. ${e}` + ); + + // restore error stack to point to a source of the problem. + newError.stack = `[${e.stack}]`; + throw newError; + } + } +} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/index.ts new file mode 100644 index 0000000000000..22733dcbc531b --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { model } from './model'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.mocks.ts new file mode 100644 index 0000000000000..70faab1fdc8d5 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.mocks.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 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. + */ + +const realStages = jest.requireActual('./stages'); + +export const StageMocks = Object.keys(realStages).reduce((mocks, key) => { + mocks[key] = jest.fn().mockImplementation((state: unknown) => state); + return mocks; +}, {} as Record); + +jest.doMock('./stages', () => { + return StageMocks; +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts new file mode 100644 index 0000000000000..b6c91e702bd2b --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StageMocks } from './model.test.mocks'; +import * as Either from 'fp-ts/lib/Either'; +import { createContextMock, MockedMigratorContext } from '../test_helpers'; +import type { RetryableEsClientError } from '../../actions'; +import type { State, BaseState, FatalState } from '../state'; +import type { StateActionResponse } from './types'; +import { model } from './model'; + +describe('model', () => { + let context: MockedMigratorContext; + + beforeEach(() => { + context = createContextMock(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const baseState: BaseState = { + controlState: '42', + retryCount: 0, + retryDelay: 0, + logs: [], + }; + + const retryableError: RetryableEsClientError = { + type: 'retryable_es_client_error', + message: 'snapshot_in_progress_exception', + }; + + describe('retry behavior', () => { + test('increments retryCount, exponential retryDelay if an action fails with a retryable_es_client_error', () => { + let state: State = { + ...baseState, + controlState: 'INIT', + }; + + const states = new Array(5).fill(1).map(() => { + state = model(state, Either.left(retryableError), context); + return state; + }); + const retryState = states.map(({ retryCount, retryDelay }) => ({ retryCount, retryDelay })); + expect(retryState).toMatchInlineSnapshot(` + Array [ + Object { + "retryCount": 1, + "retryDelay": 2000, + }, + Object { + "retryCount": 2, + "retryDelay": 4000, + }, + Object { + "retryCount": 3, + "retryDelay": 8000, + }, + Object { + "retryCount": 4, + "retryDelay": 16000, + }, + Object { + "retryCount": 5, + "retryDelay": 32000, + }, + ] + `); + }); + + test('resets retryCount, retryDelay when an action succeeds', () => { + const state: State = { + ...baseState, + controlState: 'INIT', + retryCount: 5, + retryDelay: 32000, + }; + const res: StateActionResponse<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: {}, + mappings: { properties: {} }, + settings: {}, + }, + }); + const newState = model(state, res, context); + + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + + test('terminates to FATAL after retryAttempts retries', () => { + const state: State = { + ...baseState, + controlState: 'INIT', + retryCount: 15, + retryDelay: 64000, + }; + + const newState = model(state, Either.left(retryableError), context) as FatalState; + + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"Unable to complete the INIT step after 15 attempts, terminating. The last failure message was: snapshot_in_progress_exception"` + ); + }); + }); + + describe('dispatching to correct stage', () => { + test('dispatching INIT state', () => { + const state: State = { + ...baseState, + controlState: 'INIT', + }; + const res: StateActionResponse<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: {}, + mappings: { properties: {} }, + settings: {}, + }, + }); + model(state, res, context); + + expect(StageMocks.init).toHaveBeenCalledTimes(1); + expect(StageMocks.init).toHaveBeenCalledWith(state, res, context); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.ts new file mode 100644 index 0000000000000..2ea48bd1a58af --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { State, AllActionStates } from '../state'; +import type { ResponseType } from '../next'; +import { delayRetryState, resetRetryState } from '../../model/retry_state'; +import { throwBadControlState } from '../../model/helpers'; +import { isTypeof } from '../actions'; +import { MigratorContext } from '../context'; +import * as Stages from './stages'; +import { StateActionResponse } from './types'; + +export const model = ( + current: State, + response: ResponseType, + context: MigratorContext +): State => { + if (Either.isLeft(response)) { + if (isTypeof(response.left, 'retryable_es_client_error')) { + return delayRetryState(current, response.left.message, context.maxRetryAttempts); + } + } else { + current = resetRetryState(current); + } + + switch (current.controlState) { + case 'INIT': + return Stages.init(current, response as StateActionResponse<'INIT'>, context); + case 'DONE': + case 'FATAL': + // The state-action machine will never call the model in the terminating states + return throwBadControlState(current as never); + default: + return throwBadControlState(current); + } +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/index.ts new file mode 100644 index 0000000000000..aa12c6bed1b22 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { init } from './init'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts new file mode 100644 index 0000000000000..8105449c7fce8 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { createContextMock, MockedMigratorContext } from '../../test_helpers'; +import type { InitState } from '../../state'; +import type { StateActionResponse } from '../types'; +import { init } from './init'; + +describe('Action: init', () => { + let context: MockedMigratorContext; + + const createState = (parts: Partial = {}): InitState => ({ + controlState: 'INIT', + retryDelay: 0, + retryCount: 0, + logs: [], + ...parts, + }); + + beforeEach(() => { + context = createContextMock(); + }); + + test('INIT -> DONE because its not implemented yet', () => { + const state = createState(); + const res: StateActionResponse<'INIT'> = Either.right({ + '.kibana_8.7.0_001': { + aliases: { + '.kibana': {}, + '.kibana_8.7.0': {}, + }, + mappings: { properties: {} }, + settings: {}, + }, + }); + + const newState = init(state, res, context); + + expect(newState.controlState).toEqual('DONE'); + }); + + test('INIT -> INIT when cluster routing allocation is incompatible', () => { + const state = createState(); + const res: StateActionResponse<'INIT'> = Either.left({ + type: 'incompatible_cluster_routing_allocation', + }); + + const newState = init(state, res, context); + + expect(newState.controlState).toEqual('INIT'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + expect(newState.logs).toHaveLength(1); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts new file mode 100644 index 0000000000000..78dccf237afca --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.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 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 { delayRetryState } from '../../../model/retry_state'; +import { throwBadResponse } from '../../../model/helpers'; +import { isTypeof } from '../../actions'; +import type { State } from '../../state'; +import type { ModelStage } from '../types'; + +export const init: ModelStage<'INIT', 'DONE' | 'FATAL'> = (state, res, context): State => { + if (Either.isLeft(res)) { + const left = res.left; + if (isTypeof(left, 'incompatible_cluster_routing_allocation')) { + const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${context.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; + return delayRetryState(state, retryErrorMessage, context.maxRetryAttempts); + } else { + return throwBadResponse(state, left); + } + } + + // nothing implemented yet, just going to 'DONE' + return { + ...state, + controlState: 'DONE', + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/types.ts new file mode 100644 index 0000000000000..6c8227794bb83 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/types.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 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 { ExcludeRetryableEsError } from '../../model/types'; +import type { MigratorContext } from '../context'; +import type { + AllActionStates, + AllControlStates, + StateFromActionState, + StateFromControlState, +} from '../state'; +import type { ResponseType } from '../next'; + +/** + * Utility type used to define the input of stage functions + */ +export type StateActionResponse = ExcludeRetryableEsError< + ResponseType +>; + +/** + * Defines a stage delegation function for the model + */ +export type ModelStage = ( + state: StateFromActionState, + res: StateActionResponse, + context: MigratorContext +) => StateFromControlState; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts new file mode 100644 index 0000000000000..329ba194b5957 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { AllActionStates, InitState, State } from './state'; +import type { MigratorContext } from './context'; +import * as Actions from './actions'; + +export type ActionMap = ReturnType; + +/** + * The response type of the provided control state's action. + * + * E.g. given 'INIT', provides the response type of the action triggered by + * `next` in the 'INIT' control state. + */ +export type ResponseType = Awaited< + ReturnType> +>; + +export const nextActionMap = (context: MigratorContext) => { + return { + INIT: (state: InitState) => + Actions.init({ client: context.elasticsearchClient, indices: [context.indexPrefix] }), + }; +}; + +export const next = (context: MigratorContext) => { + const map = nextActionMap(context); + + return (state: State) => { + const delay = any>(fn: F): (() => ReturnType) => { + return () => { + return state.retryDelay > 0 + ? new Promise((resolve) => setTimeout(resolve, state.retryDelay)).then(fn) + : fn(); + }; + }; + + if (state.controlState === 'DONE' || state.controlState === 'FATAL') { + // Return null if we're in one of the terminating states + return null; + } else { + // Otherwise return the delayed action + // We use an explicit cast as otherwise TS infers `(state: never) => ...` + // here because state is inferred to be the intersection of all states + // instead of the union. + const nextAction = map[state.controlState] as ( + state: State + ) => ReturnType; + return delay(nextAction(state)); + } + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/run_zdt_migration.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/run_zdt_migration.ts new file mode 100644 index 0000000000000..8a2686e23764b --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/run_zdt_migration.ts @@ -0,0 +1,59 @@ +/* + * Copyright 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 { Logger } from '@kbn/logging'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { + ISavedObjectTypeRegistry, + ISavedObjectsSerializer, +} from '@kbn/core-saved-objects-server'; +import { + type SavedObjectsMigrationConfigType, + type MigrationResult, +} from '@kbn/core-saved-objects-base-server-internal'; +import type { VersionedTransformer } from '../document_migrator'; +import { buildMigratorConfigs } from './utils'; +import { migrateIndex } from './migrate_index'; + +export interface RunZeroDowntimeMigrationOpts { + /** The kibana system index prefix. e.g `.kibana` */ + kibanaIndexPrefix: string; + /** The SO type registry to use for the migration */ + typeRegistry: ISavedObjectTypeRegistry; + /** Logger to use for migration output */ + logger: Logger; + /** The document migrator to use to convert the document */ + documentMigrator: VersionedTransformer; + /** The migration config to use for the migration */ + migrationConfig: SavedObjectsMigrationConfigType; + /** docLinks contract to use to link to documentation */ + docLinks: DocLinksServiceStart; + /** SO serializer to use for migration */ + serializer: ISavedObjectsSerializer; + /** The client to use for communications with ES */ + elasticsearchClient: ElasticsearchClient; +} + +export const runZeroDowntimeMigration = async ( + options: RunZeroDowntimeMigrationOpts +): Promise => { + const migratorConfigs = buildMigratorConfigs({ + kibanaIndexPrefix: options.kibanaIndexPrefix, + typeRegistry: options.typeRegistry, + }); + + return await Promise.all( + migratorConfigs.map((migratorConfig) => { + return migrateIndex({ + ...options, + ...migratorConfig, + }); + }) + ); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/create_initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/create_initial_state.ts new file mode 100644 index 0000000000000..ceb68b42b4a72 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/create_initial_state.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 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 { InitState, State } from './types'; +import type { MigratorContext } from '../context'; + +export const createInitialState = (context: MigratorContext): State => { + const initialState: InitState = { + controlState: 'INIT', + logs: [], + retryCount: 0, + retryDelay: 0, + }; + return initialState; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/index.ts new file mode 100644 index 0000000000000..bff3ea15da4c2 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/index.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 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 type { + BaseState, + InitState, + DoneState, + FatalState, + State, + AllActionStates, + AllControlStates, + StateFromActionState, + StateFromControlState, +} from './types'; +export { createInitialState } from './create_initial_state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts new file mode 100644 index 0000000000000..90a888a8a947c --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { MigrationLog } from '../../types'; +import type { ControlState } from '../../state_action_machine'; + +export interface BaseState extends ControlState { + readonly retryCount: number; + readonly retryDelay: number; + readonly logs: MigrationLog[]; +} + +export interface InitState extends BaseState { + readonly controlState: 'INIT'; +} + +/** Migration completed successfully */ +export interface DoneState extends BaseState { + readonly controlState: 'DONE'; +} + +/** Migration terminated with a failure */ +export interface FatalState extends BaseState { + readonly controlState: 'FATAL'; + /** The reason the migration was terminated */ + readonly reason: string; +} + +export type State = InitState | DoneState | FatalState; + +export type AllControlStates = State['controlState']; + +export type AllActionStates = Exclude; + +/** + * Manually maintained reverse-lookup map used by `StateFromAction` + */ +export interface ControlStateMap { + INIT: InitState; + FATAL: FatalState; + DONE: DoneState; +} + +/** + * Utility type to reverse lookup an `AllControlStates` to it's corresponding State subtype. + */ +export type StateFromControlState = ControlStateMap[T]; + +/** + * Utility type to reverse lookup an `AllActionStates` to it's corresponding State subtype. + */ +export type StateFromActionState = StateFromControlState; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts new file mode 100644 index 0000000000000..ded69aa02e7de --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.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. + */ + +import { + ElasticsearchClientMock, + elasticsearchClientMock, +} from '@kbn/core-elasticsearch-client-server-mocks'; +import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; +import { serializerMock } from '@kbn/core-saved-objects-base-server-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import type { MigratorContext } from '../context'; + +export type MockedMigratorContext = Omit & { + elasticsearchClient: ElasticsearchClientMock; +}; + +export const createContextMock = ( + parts: Partial = {} +): MockedMigratorContext => { + const typeRegistry = new SavedObjectTypeRegistry(); + + return { + indexPrefix: '.kibana', + types: ['foo', 'bar'], + elasticsearchClient: elasticsearchClientMock.createElasticsearchClient(), + maxRetryAttempts: 15, + migrationDocLinks: docLinksServiceMock.createSetupContract().links.kibanaUpgradeSavedObjects, + typeRegistry, + serializer: serializerMock.create(), + ...parts, + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/index.ts new file mode 100644 index 0000000000000..8b0c329317c03 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { createContextMock, type MockedMigratorContext } from './context'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_migrator_configs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_migrator_configs.ts new file mode 100644 index 0000000000000..79634c456c5d7 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_migrator_configs.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 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 { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; + +export interface MigratorConfig { + /** The index prefix for this migrator. e.g '.kibana' */ + indexPrefix: string; + /** The id of the types this migrator is in charge of */ + types: string[]; +} + +export const buildMigratorConfigs = ({ + typeRegistry, + kibanaIndexPrefix, +}: { + typeRegistry: ISavedObjectTypeRegistry; + kibanaIndexPrefix: string; +}): MigratorConfig[] => { + const configMap = new Map(); + typeRegistry.getAllTypes().forEach((type) => { + const typeIndexPrefix = type.indexPattern ?? kibanaIndexPrefix; + if (!configMap.has(typeIndexPrefix)) { + configMap.set(typeIndexPrefix, { + indexPrefix: typeIndexPrefix, + types: [], + }); + } + const migratorConfig = configMap.get(typeIndexPrefix)!; + migratorConfig.types.push(type.name); + }); + return [...configMap.values()]; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts new file mode 100644 index 0000000000000..f96ea531e460d --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { buildMigratorConfigs, type MigratorConfig } from './get_migrator_configs'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json index d1751da1050f5..e56ee4e2c3234 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json @@ -30,6 +30,7 @@ "@kbn/doc-links", "@kbn/safer-lodash-set", "@kbn/logging-mocks", + "@kbn/core-saved-objects-base-server-mocks", ], "exclude": [ "target/**/*", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts index e7df2a0363a26..a0d7ac83fe74a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts @@ -12,7 +12,10 @@ import type { IKibanaMigrator, KibanaMigratorStatus, } from '@kbn/core-saved-objects-base-server-internal'; -import { buildActiveMappings, mergeTypes } from '@kbn/core-saved-objects-migration-server-internal'; +import { + buildActiveMappings, + buildTypesMappings, +} from '@kbn/core-saved-objects-migration-server-internal'; const defaultSavedObjectTypes: SavedObjectsType[] = [ { @@ -57,7 +60,7 @@ const createMigrator = ( ), }; - mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(mergeTypes(types))); + mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(buildTypesMappings(types))); mockMigrator.migrateDocument.mockImplementation((doc) => doc); return mockMigrator; }; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 6afe9eb18b4f9..fc2cc5a4c91bc 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -419,6 +419,7 @@ describe('SavedObjectsService', () => { startDeps.node = nodeServiceMock.createInternalStartContract({ ui: true, backgroundTasks: true, + migrator: false, }); await soService.start(startDeps); @@ -444,6 +445,7 @@ describe('SavedObjectsService', () => { startDeps.node = nodeServiceMock.createInternalStartContract({ ui: true, backgroundTasks: false, + migrator: false, }); await soService.start(startDeps); @@ -469,6 +471,7 @@ describe('SavedObjectsService', () => { startDeps.node = nodeServiceMock.createInternalStartContract({ ui: false, backgroundTasks: true, + migrator: false, }); await soService.start(startDeps); diff --git a/packages/kbn-alerts-as-data-utils/index.ts b/packages/kbn-alerts-as-data-utils/index.ts new file mode 100644 index 0000000000000..7b2cad2ca5440 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/field_maps'; diff --git a/packages/kbn-alerts-as-data-utils/kibana.jsonc b/packages/kbn-alerts-as-data-utils/kibana.jsonc new file mode 100644 index 0000000000000..07e8490dde7b5 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/alerts-as-data-utils", + "owner": "@elastic/response-ops" +} diff --git a/packages/kbn-alerts-as-data-utils/package.json b/packages/kbn-alerts-as-data-utils/package.json new file mode 100644 index 0000000000000..25aa26b3d435c --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/alerts-as-data-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts similarity index 77% rename from x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts rename to packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 4613415e0fa00..8e5a606a91017 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -1,16 +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. + * 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 { ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, + ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, ALERT_RULE_CONSUMER, @@ -27,92 +31,93 @@ import { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, } from '@kbn/rule-data-utils'; export const alertFieldMap = { - [ALERT_RULE_PARAMETERS]: { - type: 'object', - enabled: false, + [ALERT_ACTION_GROUP]: { + type: 'keyword', + array: false, required: false, }, - [ALERT_RULE_TYPE_ID]: { + [ALERT_CASE_IDS]: { type: 'keyword', + array: true, + required: false, + }, + [ALERT_DURATION]: { + type: 'long', array: false, - required: true, + required: false, }, - [ALERT_RULE_CONSUMER]: { - type: 'keyword', + [ALERT_END]: { + type: 'date', array: false, - required: true, + required: false, }, - [ALERT_RULE_PRODUCER]: { - type: 'keyword', + [ALERT_FLAPPING]: { + type: 'boolean', array: false, - required: true, + required: false, }, - [SPACE_IDS]: { - type: 'keyword', + [ALERT_FLAPPING_HISTORY]: { + type: 'boolean', array: true, - required: true, - }, - [ALERT_UUID]: { - type: 'keyword', - array: false, - required: true, + required: false, }, - [ALERT_ID]: { + [ALERT_INSTANCE_ID]: { type: 'keyword', array: false, required: true, }, - [ALERT_START]: { + [ALERT_LAST_DETECTED]: { type: 'date', - array: false, required: false, - }, - [ALERT_TIME_RANGE]: { - type: 'date_range', - format: 'epoch_millis||strict_date_optional_time', array: false, - required: false, }, - [ALERT_END]: { - type: 'date', + [ALERT_REASON]: { + type: 'keyword', array: false, required: false, }, - [ALERT_DURATION]: { - type: 'long', + [ALERT_RULE_CATEGORY]: { + type: 'keyword', array: false, - required: false, + required: true, }, - [ALERT_STATUS]: { + [ALERT_RULE_CONSUMER]: { type: 'keyword', array: false, required: true, }, - [VERSION]: { - type: 'version', + [ALERT_RULE_EXECUTION_UUID]: { + type: 'keyword', array: false, required: false, }, - [ALERT_WORKFLOW_STATUS]: { + [ALERT_RULE_NAME]: { type: 'keyword', array: false, - required: false, + required: true, }, - [ALERT_ACTION_GROUP]: { - type: 'keyword', + [ALERT_RULE_PARAMETERS]: { array: false, + type: 'flattened', + ignore_above: 4096, required: false, }, - [ALERT_REASON]: { + [ALERT_RULE_PRODUCER]: { type: 'keyword', array: false, + required: true, + }, + [ALERT_RULE_TAGS]: { + type: 'keyword', + array: true, required: false, }, - [ALERT_RULE_CATEGORY]: { + [ALERT_RULE_TYPE_ID]: { type: 'keyword', array: false, required: true, @@ -122,26 +127,47 @@ export const alertFieldMap = { array: false, required: true, }, - [ALERT_RULE_EXECUTION_UUID]: { + [ALERT_START]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_STATUS]: { type: 'keyword', array: false, + required: true, + }, + [ALERT_TIME_RANGE]: { + type: 'date_range', + format: 'epoch_millis||strict_date_optional_time', + array: false, required: false, }, - [ALERT_RULE_NAME]: { + [ALERT_UUID]: { type: 'keyword', array: false, required: true, }, - [ALERT_RULE_TAGS]: { + [ALERT_WORKFLOW_STATUS]: { type: 'keyword', - array: true, + array: false, required: false, }, - [ALERT_FLAPPING]: { - type: 'boolean', + [SPACE_IDS]: { + type: 'keyword', + array: true, + required: true, + }, + [TIMESTAMP]: { + type: 'date', + required: true, + array: false, + }, + [VERSION]: { + type: 'version', array: false, required: false, }, -}; +} as const; export type AlertFieldMap = typeof alertFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts new file mode 100644 index 0000000000000..9294a12b4ce50 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts @@ -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 { EcsFlat } from '@kbn/ecs'; +import { EcsMetadata, FieldMap } from './types'; + +export const ecsFieldMap: FieldMap = Object.keys(EcsFlat).reduce((acc, currKey) => { + const value: EcsMetadata = EcsFlat[currKey as keyof typeof EcsFlat]; + return { + ...acc, + [currKey]: { + type: value.type, + array: value.normalize.includes('array'), + required: !!value.required, + ...(value.scaling_factor ? { scaling_factor: value.scaling_factor } : {}), + ...(value.ignore_above ? { ignore_above: value.ignore_above } : {}), + ...(value.multi_fields ? { multi_fields: value.multi_fields } : {}), + }, + }; +}, {}); + +export type EcsFieldMap = typeof ecsFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts new file mode 100644 index 0000000000000..9aef7690b343c --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright 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 './alert_field_map'; +export * from './ecs_field_map'; +export * from './legacy_alert_field_map'; +export type { FieldMap, MultiField } from './types'; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts new file mode 100644 index 0000000000000..6faa403188fdb --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +} from '@kbn/rule-data-utils'; + +export const legacyAlertFieldMap = { + [ALERT_RISK_SCORE]: { + type: 'float', + array: false, + required: false, + }, + [ALERT_RULE_AUTHOR]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_CREATED_AT]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_RULE_CREATED_BY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_DESCRIPTION]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_ENABLED]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_FROM]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_INTERVAL]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_LICENSE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_NOTE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_REFERENCES]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_RULE_RULE_ID]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_RULE_NAME_OVERRIDE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_TO]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_TYPE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_UPDATED_AT]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_RULE_UPDATED_BY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_VERSION]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_SEVERITY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_DOCS_COUNT]: { + type: 'long', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_END]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_FIELD]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_SUPPRESSION_START]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_VALUE]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_SYSTEM_STATUS]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_WORKFLOW_REASON]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_WORKFLOW_USER]: { + type: 'keyword', + array: false, + required: false, + }, + // get these from ecs field map when available + [ECS_VERSION]: { + type: 'keyword', + array: false, + required: false, + }, + [EVENT_ACTION]: { + type: 'keyword', + array: false, + required: false, + }, + [EVENT_KIND]: { + type: 'keyword', + array: false, + required: false, + }, + [TAGS]: { + type: 'keyword', + array: true, + required: false, + }, +} as const; + +export type LegacyAlertFieldMap = typeof legacyAlertFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/types.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/types.ts new file mode 100644 index 0000000000000..04f9d045f6e28 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/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 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 AllowedValue { + description?: string; + name?: string; +} + +export interface MultiField { + flat_name: string; + name: string; + type: string; +} + +export interface EcsMetadata { + allowed_values?: AllowedValue[]; + dashed_name: string; + description: string; + doc_values?: boolean; + example?: string | number | boolean; + flat_name: string; + ignore_above?: number; + index?: boolean; + level: string; + multi_fields?: MultiField[]; + name: string; + normalize: string[]; + required?: boolean; + scaling_factor?: number; + short: string; + type: string; +} + +export interface FieldMap { + [key: string]: { + type: string; + required: boolean; + array?: boolean; + doc_values?: boolean; + enabled?: boolean; + format?: string; + ignore_above?: number; + multi_fields?: MultiField[]; + index?: boolean; + path?: string; + scaling_factor?: number; + dynamic?: boolean | 'strict'; + }; +} diff --git a/packages/kbn-alerts-as-data-utils/tsconfig.json b/packages/kbn-alerts-as-data-utils/tsconfig.json new file mode 100644 index 0000000000000..00b7ffc082c95 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/ecs", + "@kbn/rule-data-utils", + ] +} diff --git a/packages/kbn-expandable-flyout/README.md b/packages/kbn-expandable-flyout/README.md new file mode 100644 index 0000000000000..8d21caba73a21 --- /dev/null +++ b/packages/kbn-expandable-flyout/README.md @@ -0,0 +1,66 @@ +# @kbn/expandable-flyout + +## Purpose + +This package offers an expandable flyout UI component and a way to manage the data displayed in it. The component leverages the [EuiFlyout](https://github.com/elastic/eui/tree/main/src/components/flyout) from the EUI library. + +The flyout is composed of 3 sections: +- a right section (primary section) that opens first +- a left wider section to show more details +- a preview section, that overlays the right section. This preview section can display multiple panels one after the other and displays a `Back` button + +At the moment, displaying more than one flyout within the same plugin might be complicated, unless there are in difference areas in the codebase and the contexts don't conflict with each other. + +## What the package offers + +The ExpandableFlyout [React component](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/index) that renders the UI. + +The ExpandableFlyout [React context](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/context) that exposes the following api: +- **openFlyout**: open the flyout with a set of panels +- **openFlyoutRightPanel**: open a right panel +- **openFlyoutLeftPanel**: open a left panel +- **openFlyoutPreviewPanel**: open a preview panel +- **closeFlyoutRightPanel**: close the right panel +- **closeFlyoutLeftPanel**: close the left panel +- **closeFlyoutPreviewPanel**: close the preview panels +- **previousFlyoutPreviewPanel**: navigate to the previous preview panel +- **closeFlyout**: close the flyout + +To retrieve the flyout's layout (left, right and preview panels), you can use the **panels** from the same [React context](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/context); + +- To have more details about how these above api work, see the code documentation [here](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/utils/helpers). + +## Usage + +To use the expandable flyout in your plugin, first you need wrap your code with the context provider at a high enough level as follows: +```typescript jsx + + + ... + + +``` + +Then use the React UI component where you need: + +```typescript jsx + +``` +where `myPanels` is a list of all the panels that can be rendered in the flyout (see interface [here](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/index)). + + +## Terminology + +### Section + +One of the 3 areas of the flyout (left, right or preview). + +### Panel + +A set of properties defining what's displayed in one of the flyout section. + +## Future work + +- currently the panels are aware of their width. This should be changed and the width of the left, right and preview sections should be handled by the flyout itself +- add the feature to save the flyout state (layout) to the url +- introduce the notion of scope to be able to handle more than one flyout per plugin?? \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/index.ts b/packages/kbn-expandable-flyout/index.ts new file mode 100644 index 0000000000000..e2ce15d85a399 --- /dev/null +++ b/packages/kbn-expandable-flyout/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ExpandableFlyout } from './src'; +export { ExpandableFlyoutProvider, useExpandableFlyoutContext } from './src/context'; + +export type { ExpandableFlyoutProps } from './src'; +export type { FlyoutPanel } from './src/types'; diff --git a/packages/kbn-expandable-flyout/jest.config.js b/packages/kbn-expandable-flyout/jest.config.js new file mode 100644 index 0000000000000..f861f9b122fd5 --- /dev/null +++ b/packages/kbn-expandable-flyout/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-expandable-flyout'], +}; diff --git a/packages/kbn-expandable-flyout/kibana.jsonc b/packages/kbn-expandable-flyout/kibana.jsonc new file mode 100644 index 0000000000000..b4f63cca6bf91 --- /dev/null +++ b/packages/kbn-expandable-flyout/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/expandable-flyout", + "owner": "@elastic/security-threat-hunting-investigations" +} diff --git a/packages/kbn-expandable-flyout/package.json b/packages/kbn-expandable-flyout/package.json new file mode 100644 index 0000000000000..a2e826c042842 --- /dev/null +++ b/packages/kbn-expandable-flyout/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/expandable-flyout", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/src/actions.ts b/packages/kbn-expandable-flyout/src/actions.ts new file mode 100644 index 0000000000000..5709214394303 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/actions.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FlyoutPanel } from './types'; + +export enum ActionType { + openFlyout = 'open_flyout', + openRightPanel = 'open_right_panel', + openLeftPanel = 'open_left_panel', + openPreviewPanel = 'open_preview_panel', + closeRightPanel = 'close_right_panel', + closeLeftPanel = 'close_left_panel', + closePreviewPanel = 'close_preview_panel', + previousPreviewPanel = 'previous_preview_panel', + closeFlyout = 'close_flyout', +} + +export type Action = + | { + type: ActionType.openFlyout; + payload: { + right?: FlyoutPanel; + left?: FlyoutPanel; + preview?: FlyoutPanel; + }; + } + | { + type: ActionType.openRightPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.openLeftPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.openPreviewPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.closeRightPanel; + } + | { + type: ActionType.closeLeftPanel; + } + | { + type: ActionType.closePreviewPanel; + } + | { + type: ActionType.previousPreviewPanel; + } + | { + type: ActionType.closeFlyout; + }; diff --git a/packages/kbn-expandable-flyout/src/components/left_section.tsx b/packages/kbn-expandable-flyout/src/components/left_section.tsx new file mode 100644 index 0000000000000..ddf53efbad2b8 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/left_section.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { LEFT_SECTION } from './test_ids'; + +interface LeftSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; +} + +/** + * Left section of the expanded flyout rendering a panel + */ +export const LeftSection: React.FC = ({ component, width }: LeftSectionProps) => { + return ( + + + {component} + + + ); +}; + +LeftSection.displayName = 'LeftSection'; diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx new file mode 100644 index 0000000000000..41926400e11f5 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 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 { render } from '@testing-library/react'; +import { PreviewSection } from './preview_section'; +import { PREVIEW_SECTION_BACK_BUTTON, PREVIEW_SECTION_CLOSE_BUTTON } from './test_ids'; +import { ExpandableFlyoutContext } from '../context'; + +describe('PreviewSection', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: {}, + preview: [ + { + id: 'key', + }, + ], + }, + } as unknown as ExpandableFlyoutContext; + + it('should render close button in header', () => { + const component =
      {'component'}
      ; + const width = 500; + const showBackButton = false; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION_CLOSE_BUTTON)).toBeInTheDocument(); + }); + + it('should render back button in header', () => { + const component =
      {'component'}
      ; + const width = 500; + const showBackButton = true; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION_BACK_BUTTON)).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.tsx new file mode 100644 index 0000000000000..e474d1204bf03 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/preview_section.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 { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + useEuiTheme, +} from '@elastic/eui'; +import React from 'react'; +import { css } from '@emotion/react'; +import { + PREVIEW_SECTION, + PREVIEW_SECTION_BACK_BUTTON, + PREVIEW_SECTION_CLOSE_BUTTON, +} from './test_ids'; +import { useExpandableFlyoutContext } from '../..'; +import { BACK_BUTTON, CLOSE_BUTTON } from './translations'; + +interface PreviewSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number | undefined; + /** + * Display the back button in the header + */ + showBackButton: boolean; +} + +/** + * Preview section of the expanded flyout rendering one or multiple panels. + * Will display a back and close button in the header for the previous and close feature respectively. + */ +export const PreviewSection: React.FC = ({ + component, + showBackButton, + width, +}: PreviewSectionProps) => { + const { euiTheme } = useEuiTheme(); + const { closePreviewPanel, previousPreviewPanel } = useExpandableFlyoutContext(); + + const previewWith: string = width ? `${width}px` : '0px'; + + const closeButton = ( + + closePreviewPanel()} + data-test-subj={PREVIEW_SECTION_CLOSE_BUTTON} + aria-label={CLOSE_BUTTON} + /> + + ); + const header = showBackButton ? ( + + + previousPreviewPanel()} + data-test-subj={PREVIEW_SECTION_BACK_BUTTON} + aria-label={BACK_BUTTON} + > + {BACK_BUTTON} + + + {closeButton} + + ) : ( + {closeButton} + ); + + return ( + <> +
      +
      + + {header} + {component} + +
      + + ); +}; + +PreviewSection.displayName = 'PreviewSection'; diff --git a/packages/kbn-expandable-flyout/src/components/right_section.tsx b/packages/kbn-expandable-flyout/src/components/right_section.tsx new file mode 100644 index 0000000000000..d018dd8721ee7 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/right_section.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { RIGHT_SECTION } from './test_ids'; + +interface RightSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; +} + +/** + * Right section of the expanded flyout rendering a panel + */ +export const RightSection: React.FC = ({ + component, + width, +}: RightSectionProps) => { + return ( + + + {component} + + + ); +}; + +RightSection.displayName = 'RightSection'; diff --git a/packages/kbn-expandable-flyout/src/components/test_ids.ts b/packages/kbn-expandable-flyout/src/components/test_ids.ts new file mode 100644 index 0000000000000..38dd9231712c4 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/test_ids.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. + */ + +export const RIGHT_SECTION = 'rightSection'; + +export const LEFT_SECTION = 'leftSection'; + +export const PREVIEW_SECTION = 'previewSection'; + +export const PREVIEW_SECTION_CLOSE_BUTTON = 'previewSectionCloseButton'; + +export const PREVIEW_SECTION_BACK_BUTTON = 'previewSectionBackButton'; diff --git a/packages/kbn-expandable-flyout/src/components/translations.ts b/packages/kbn-expandable-flyout/src/components/translations.ts new file mode 100644 index 0000000000000..8b6646fa69df3 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/translations.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 { i18n } from '@kbn/i18n'; + +export const BACK_BUTTON = i18n.translate('expandableFlyout.previewSection.backButton', { + defaultMessage: 'Back', +}); + +export const CLOSE_BUTTON = i18n.translate('expandableFlyout.previewSection.closeButton', { + defaultMessage: 'Close', +}); diff --git a/packages/kbn-expandable-flyout/src/context.tsx b/packages/kbn-expandable-flyout/src/context.tsx new file mode 100644 index 0000000000000..89e8210e9578f --- /dev/null +++ b/packages/kbn-expandable-flyout/src/context.tsx @@ -0,0 +1,172 @@ +/* + * Copyright 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, { createContext, useCallback, useContext, useMemo, useReducer } from 'react'; +import { ActionType } from './actions'; +import { reducer, State } from './reducer'; +import type { FlyoutPanel } from './types'; +import { initialState } from './reducer'; + +export interface ExpandableFlyoutContext { + /** + * Right, left and preview panels + */ + panels: State; + /** + * Open the flyout with left, right and/or preview panels + */ + openFlyout: (panels: { left?: FlyoutPanel; right?: FlyoutPanel; preview?: FlyoutPanel }) => void; + /** + * Replaces the current right panel with a new one + */ + openRightPanel: (panel: FlyoutPanel) => void; + /** + * Replaces the current left panel with a new one + */ + openLeftPanel: (panel: FlyoutPanel) => void; + /** + * Add a new preview panel to the list of current preview panels + */ + openPreviewPanel: (panel: FlyoutPanel) => void; + /** + * Closes right panel + */ + closeRightPanel: () => void; + /** + * Closes left panel + */ + closeLeftPanel: () => void; + /** + * Closes all preview panels + */ + closePreviewPanel: () => void; + /** + * Go back to previous preview panel + */ + previousPreviewPanel: () => void; + /** + * Close all panels and closes flyout + */ + closeFlyout: () => void; +} + +export const ExpandableFlyoutContext = createContext( + undefined +); + +export interface ExpandableFlyoutProviderProps { + /** + * React children + */ + children: React.ReactNode; +} + +/** + * Wrap your plugin with this context for the ExpandableFlyout React component. + */ +export const ExpandableFlyoutProvider = ({ children }: ExpandableFlyoutProviderProps) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const openPanels = useCallback( + ({ + right, + left, + preview, + }: { + right?: FlyoutPanel; + left?: FlyoutPanel; + preview?: FlyoutPanel; + }) => dispatch({ type: ActionType.openFlyout, payload: { left, right, preview } }), + [dispatch] + ); + + const openRightPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openRightPanel, payload: panel }), + [dispatch] + ); + + const openLeftPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openLeftPanel, payload: panel }), + [dispatch] + ); + + const openPreviewPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openPreviewPanel, payload: panel }), + [dispatch] + ); + + const closeRightPanel = useCallback( + () => dispatch({ type: ActionType.closeRightPanel }), + [dispatch] + ); + + const closeLeftPanel = useCallback( + () => dispatch({ type: ActionType.closeLeftPanel }), + [dispatch] + ); + + const closePreviewPanel = useCallback( + () => dispatch({ type: ActionType.closePreviewPanel }), + [dispatch] + ); + + const previousPreviewPanel = useCallback( + () => dispatch({ type: ActionType.previousPreviewPanel }), + [dispatch] + ); + + const closePanels = useCallback(() => dispatch({ type: ActionType.closeFlyout }), [dispatch]); + + const contextValue = useMemo( + () => ({ + panels: state, + openFlyout: openPanels, + openRightPanel, + openLeftPanel, + openPreviewPanel, + closeRightPanel, + closeLeftPanel, + closePreviewPanel, + closeFlyout: closePanels, + previousPreviewPanel, + }), + [ + state, + openPanels, + openRightPanel, + openLeftPanel, + openPreviewPanel, + closeRightPanel, + closeLeftPanel, + closePreviewPanel, + closePanels, + previousPreviewPanel, + ] + ); + + return ( + + {children} + + ); +}; + +/** + * Retrieve context's properties + */ +export const useExpandableFlyoutContext = (): ExpandableFlyoutContext => { + const contextValue = useContext(ExpandableFlyoutContext); + + if (!contextValue) { + throw new Error( + 'ExpandableFlyoutContext can only be used within ExpandableFlyoutContext provider' + ); + } + + return contextValue; +}; diff --git a/packages/kbn-expandable-flyout/src/index.test.tsx b/packages/kbn-expandable-flyout/src/index.test.tsx new file mode 100644 index 0000000000000..fd1d1990ffbad --- /dev/null +++ b/packages/kbn-expandable-flyout/src/index.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 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 { render } from '@testing-library/react'; +import { Panel } from './types'; +import { ExpandableFlyout } from '.'; +import { LEFT_SECTION, PREVIEW_SECTION, RIGHT_SECTION } from './components/test_ids'; +import { ExpandableFlyoutContext } from './context'; + +describe('ExpandableFlyout', () => { + const registeredPanels: Panel[] = [ + { + key: 'key', + width: 500, + component: () =>
      {'component'}
      , + }, + ]; + const onClose = () => window.alert('closed'); + + it(`shouldn't render flyout if no panels`, () => { + const context: ExpandableFlyoutContext = { + panels: { + right: undefined, + left: undefined, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const result = render( + + + + ); + + expect(result.asFragment()).toMatchInlineSnapshot(``); + }); + + it('should render right section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: { + id: 'key', + }, + left: {}, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(RIGHT_SECTION)).toBeInTheDocument(); + }); + + it('should render left section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: { + id: 'key', + }, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(LEFT_SECTION)).toBeInTheDocument(); + }); + + it('should render preview section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: {}, + preview: [ + { + id: 'key', + }, + ], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION)).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/index.tsx b/packages/kbn-expandable-flyout/src/index.tsx new file mode 100644 index 0000000000000..80dd1d425f2a2 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/index.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useMemo } from 'react'; +import { css } from '@emotion/react'; +import type { EuiFlyoutProps } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlyout } from '@elastic/eui'; +import { useExpandableFlyoutContext } from './context'; +import { PreviewSection } from './components/preview_section'; +import { RightSection } from './components/right_section'; +import type { FlyoutPanel, Panel } from './types'; +import { LeftSection } from './components/left_section'; + +export interface ExpandableFlyoutProps extends EuiFlyoutProps { + /** + * List of all registered panels available for render + */ + registeredPanels: Panel[]; + /** + * Propagate out EuiFlyout onClose event + */ + handleOnFlyoutClosed?: () => void; +} + +/** + * Expandable flyout UI React component. + * Displays 3 sections (right, left, preview) depending on the panels in the context. + */ +export const ExpandableFlyout: React.FC = ({ + registeredPanels, + handleOnFlyoutClosed, + ...flyoutProps +}) => { + const { panels, closeFlyout } = useExpandableFlyoutContext(); + const { left, right, preview } = panels; + + const onClose = useCallback(() => { + if (handleOnFlyoutClosed) handleOnFlyoutClosed(); + closeFlyout(); + }, [closeFlyout, handleOnFlyoutClosed]); + + const leftSection = useMemo( + () => registeredPanels.find((panel) => panel.key === left?.id), + [left, registeredPanels] + ); + + const rightSection = useMemo( + () => registeredPanels.find((panel) => panel.key === right?.id), + [right, registeredPanels] + ); + + // retrieve the last preview panel (most recent) + const mostRecentPreview = preview ? preview[preview.length - 1] : undefined; + const showBackButton = preview && preview.length > 1; + const previewSection = useMemo( + () => registeredPanels.find((panel) => panel.key === mostRecentPreview?.id), + [mostRecentPreview, registeredPanels] + ); + + // do not add the flyout to the dom if there aren't any panels to display + if (!left && !right && !preview.length) { + return <>; + } + + const width: number = (leftSection?.width ?? 0) + (rightSection?.width ?? 0); + + return ( + + + {leftSection && left ? ( + + ) : null} + {rightSection && right ? ( + + ) : null} + + + {previewSection && preview ? ( + + ) : null} + + ); +}; + +ExpandableFlyout.displayName = 'ExpandableFlyout'; diff --git a/packages/kbn-expandable-flyout/src/reducer.test.ts b/packages/kbn-expandable-flyout/src/reducer.test.ts new file mode 100644 index 0000000000000..21128ded7b58e --- /dev/null +++ b/packages/kbn-expandable-flyout/src/reducer.test.ts @@ -0,0 +1,417 @@ +/* + * Copyright 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 { FlyoutPanel } from './types'; +import { initialState, reducer, State } from './reducer'; +import { Action, ActionType } from './actions'; + +const rightPanel1: FlyoutPanel = { + id: 'right1', + path: ['path'], +}; +const leftPanel1: FlyoutPanel = { + id: 'left1', + params: { id: 'id' }, +}; +const previewPanel1: FlyoutPanel = { + id: 'preview1', + state: { id: 'state' }, +}; + +const rightPanel2: FlyoutPanel = { + id: 'right2', + path: ['path'], +}; +const leftPanel2: FlyoutPanel = { + id: 'left2', + params: { id: 'id' }, +}; +const previewPanel2: FlyoutPanel = { + id: 'preview2', + state: { id: 'state' }, +}; +describe('reducer', () => { + describe('should handle openFlyout action', () => { + it('should add panels to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel1, + left: leftPanel1, + preview: previewPanel1, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + + it('should override all panels in the state', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, { id: 'preview' }], + }; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel2, + left: leftPanel2, + preview: previewPanel2, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel2, + right: rightPanel2, + preview: [previewPanel2], + }); + }); + + it('should remove all panels despite only passing a single section ', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel2, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel2, + preview: [], + }); + }); + }); + + describe('should handle openRightPanel action', () => { + it('should add right panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openRightPanel, + payload: rightPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel1, + preview: [], + }); + }); + + it('should replace right panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openRightPanel, + payload: rightPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel2, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle openLeftPanel action', () => { + it('should add left panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openLeftPanel, + payload: leftPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: undefined, + preview: [], + }); + }); + + it('should replace only left panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openLeftPanel, + payload: leftPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel2, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle openPreviewPanel action', () => { + it('should add preview panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openPreviewPanel, + payload: previewPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: undefined, + preview: [previewPanel1], + }); + }); + + it('should add preview panel to the list of preview panels', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openPreviewPanel, + payload: previewPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, previewPanel2], + }); + }); + }); + + describe('should handle closeRightPanel action', () => { + it('should return empty state when removing right panel from empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing right panel when no right panel exist`, () => { + const state: State = { + left: leftPanel1, + right: undefined, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove right panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: undefined, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closeLeftPanel action', () => { + it('should return empty state when removing left panel on empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing left panel when no left panel exist`, () => { + const state: State = { + left: undefined, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove left panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closePreviewPanel action', () => { + it('should return empty state when removing preview panel on empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing preview panel when no preview panel exist`, () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove all preview panels', () => { + const state: State = { + left: rightPanel1, + right: leftPanel1, + preview: [previewPanel1, previewPanel2], + }; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: rightPanel1, + right: leftPanel1, + preview: [], + }); + }); + }); + + describe('should handle previousPreviewPanel action', () => { + it('should return empty state when previous preview panel on an empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when previous preview panel when no preview panel exist`, () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove only last preview panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, previewPanel2], + }; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closeFlyout action', () => { + it('should return empty state when closing flyout on an empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeFlyout, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(initialState); + }); + + it('should remove all panels', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeFlyout, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: undefined, + preview: [], + }); + }); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/reducer.ts b/packages/kbn-expandable-flyout/src/reducer.ts new file mode 100644 index 0000000000000..4901eccfc6bb4 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/reducer.ts @@ -0,0 +1,109 @@ +/* + * Copyright 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 { FlyoutPanel } from './types'; +import { Action, ActionType } from './actions'; + +export interface State { + /** + * Panel to render in the left section + */ + left: FlyoutPanel | undefined; + /** + * Panel to render in the right section + */ + right: FlyoutPanel | undefined; + /** + * Panels to render in the preview section + */ + preview: FlyoutPanel[]; +} + +export const initialState: State = { + left: undefined, + right: undefined, + preview: [], +}; + +export function reducer(state: State, action: Action) { + switch (action.type) { + /** + * Open the flyout by replacing the entire state with new panels. + */ + case ActionType.openFlyout: { + const { left, right, preview } = action.payload; + return { + left, + right, + preview: preview ? [preview] : [], + }; + } + + /** + * Opens a right section by replacing the previous right panel with the new one. + */ + case ActionType.openRightPanel: { + return { ...state, right: action.payload }; + } + + /** + * Opens a left section by replacing the previous left panel with the new one. + */ + case ActionType.openLeftPanel: { + return { ...state, left: action.payload }; + } + + /** + * Opens a preview section by adding to the array of preview panels. + */ + case ActionType.openPreviewPanel: { + return { ...state, preview: [...state.preview, action.payload] }; + } + + /** + * Closes the right section by removing the right panel. + */ + case ActionType.closeRightPanel: { + return { ...state, right: undefined }; + } + + /** + * Close the left section by removing the left panel. + */ + case ActionType.closeLeftPanel: { + return { ...state, left: undefined }; + } + + /** + * Closes the preview section by removing all the preview panels. + */ + case ActionType.closePreviewPanel: { + return { ...state, preview: [] }; + } + + /** + * Navigates to the previous preview panel by removing the last entry in the array of preview panels. + */ + case ActionType.previousPreviewPanel: { + const p: FlyoutPanel[] = [...state.preview]; + p.pop(); + return { ...state, preview: p }; + } + + /** + * Close the flyout by removing all the panels. + */ + case ActionType.closeFlyout: { + return { + left: undefined, + right: undefined, + preview: [], + }; + } + } +} diff --git a/packages/kbn-expandable-flyout/src/types.ts b/packages/kbn-expandable-flyout/src/types.ts new file mode 100644 index 0000000000000..f526832810900 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/types.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 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'; + +export interface FlyoutPanel { + /** + * Unique key to identify the panel + */ + id: string; + /** + * Any parameters necessary for the initial requests within the flyout + */ + params?: Record; + /** + * Tracks the path for what to show in a panel. We may have multiple tabs or details..., so easiest to just use a stack + */ + path?: string[]; + /** + * Tracks visual state such as whether the panel is collapsed + */ + state?: Record; +} + +export interface Panel { + /** + * Unique key used to identify the panel + */ + key?: string; + /** + * Component to be rendered + */ + component: (props: FlyoutPanel) => React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; // TODO remove this, the width shouldn't be a property of a panel, but handled at the flyout level +} diff --git a/packages/kbn-expandable-flyout/tsconfig.json b/packages/kbn-expandable-flyout/tsconfig.json new file mode 100644 index 0000000000000..d1755389bcddc --- /dev/null +++ b/packages/kbn-expandable-flyout/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n" + ] +} diff --git a/packages/kbn-rule-data-utils/index.ts b/packages/kbn-rule-data-utils/index.ts index 145cc2c5220e9..ea0028b972ed9 100644 --- a/packages/kbn-rule-data-utils/index.ts +++ b/packages/kbn-rule-data-utils/index.ts @@ -7,6 +7,7 @@ */ export * from './src/default_alerts_as_data'; +export * from './src/legacy_alerts_as_data'; export * from './src/technical_field_names'; export * from './src/alerts_as_data_rbac'; export * from './src/alerts_as_data_severity'; diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index 3a982124b58e6..34b04116b9522 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -8,6 +8,9 @@ import { ValuesType } from 'utility-types'; +const TIMESTAMP = '@timestamp' as const; + +// namespaces const KIBANA_NAMESPACE = 'kibana' as const; const ALERT_NAMESPACE = `${KIBANA_NAMESPACE}.alert` as const; const ALERT_RULE_NAMESPACE = `${ALERT_NAMESPACE}.rule` as const; @@ -21,6 +24,9 @@ const VERSION = `${KIBANA_NAMESPACE}.version` as const; // kibana.alert.action_group - framework action group ID for this alert const ALERT_ACTION_GROUP = `${ALERT_NAMESPACE}.action_group` as const; +// kibana.alert.case_ids - array of cases associated with the alert +const ALERT_CASE_IDS = `${ALERT_NAMESPACE}.case_ids` as const; + // kibana.alert.duration.us - alert duration in nanoseconds - updated each execution // that the alert is active const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const; @@ -31,8 +37,11 @@ const ALERT_END = `${ALERT_NAMESPACE}.end` as const; // kibana.alert.flapping - whether the alert is currently in a flapping state const ALERT_FLAPPING = `${ALERT_NAMESPACE}.flapping` as const; -// kibana.alert.id - alert ID, also known as alert instance ID -const ALERT_ID = `${ALERT_NAMESPACE}.id` as const; +// kibana.alert.flapping_history - whether the alert is currently in a flapping state +const ALERT_FLAPPING_HISTORY = `${ALERT_NAMESPACE}.flapping_history` as const; + +// kibana.alert.instance.id - alert ID, also known as alert instance ID +const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const; // kibana.alert.last_detected - timestamp when the alert was last seen const ALERT_LAST_DETECTED = `${ALERT_NAMESPACE}.last_detected` as const; @@ -90,10 +99,12 @@ const namespaces = { const fields = { ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -111,15 +122,24 @@ const fields = { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, }; export { + // namespaces + ALERT_NAMESPACE, + ALERT_RULE_NAMESPACE, + KIBANA_NAMESPACE, + + // fields ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -137,10 +157,8 @@ export { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, - ALERT_NAMESPACE, - ALERT_RULE_NAMESPACE, - KIBANA_NAMESPACE, }; export type DefaultAlertFieldName = ValuesType; diff --git a/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts new file mode 100644 index 0000000000000..4dd6c2be0c2a6 --- /dev/null +++ b/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ALERT_NAMESPACE, ALERT_RULE_NAMESPACE } from './default_alerts_as_data'; + +const ECS_VERSION = 'ecs.version' as const; +const EVENT_ACTION = 'event.action' as const; +const EVENT_KIND = 'event.kind' as const; +const TAGS = 'tags' as const; + +// These are the fields that are in the rule registry technical component template +// that are NOT in the framework alerts as data common component template + +// We will maintain a legacy component template that can be used by legacy +// rule registry rules with these fields. +const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; +const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const; +const ALERT_RULE_CREATED_AT = `${ALERT_RULE_NAMESPACE}.created_at` as const; +const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const; +const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const; +const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const; +const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const; +const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const; +const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const; +const ALERT_RULE_NOTE = `${ALERT_RULE_NAMESPACE}.note` as const; +const ALERT_RULE_REFERENCES = `${ALERT_RULE_NAMESPACE}.references` as const; +const ALERT_RULE_RULE_ID = `${ALERT_RULE_NAMESPACE}.rule_id` as const; +const ALERT_RULE_RULE_NAME_OVERRIDE = `${ALERT_RULE_NAMESPACE}.rule_name_override` as const; +const ALERT_RULE_TO = `${ALERT_RULE_NAMESPACE}.to` as const; +const ALERT_RULE_TYPE = `${ALERT_RULE_NAMESPACE}.type` as const; +const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const; +const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const; +const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const; +const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; +const ALERT_SUPPRESSION_META = `${ALERT_NAMESPACE}.suppression` as const; +const ALERT_SUPPRESSION_TERMS = `${ALERT_SUPPRESSION_META}.terms` as const; +const ALERT_SUPPRESSION_FIELD = `${ALERT_SUPPRESSION_TERMS}.field` as const; +const ALERT_SUPPRESSION_VALUE = `${ALERT_SUPPRESSION_TERMS}.value` as const; +const ALERT_SUPPRESSION_START = `${ALERT_SUPPRESSION_META}.start` as const; +const ALERT_SUPPRESSION_END = `${ALERT_SUPPRESSION_META}.end` as const; +const ALERT_SUPPRESSION_DOCS_COUNT = `${ALERT_SUPPRESSION_META}.docs_count` as const; +const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; +const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const; +const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const; + +export { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +}; diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 89eca0f923046..cf45162b20853 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -8,11 +8,15 @@ import { ValuesType } from 'utility-types'; import { + ALERT_NAMESPACE, + ALERT_RULE_NAMESPACE, KIBANA_NAMESPACE, ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, + ALERT_INSTANCE_ID, ALERT_REASON, ALERT_RULE_CATEGORY, ALERT_RULE_CONSUMER, @@ -29,61 +33,61 @@ import { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, - ALERT_NAMESPACE, - ALERT_RULE_NAMESPACE, } from './default_alerts_as_data'; +import { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +} from './legacy_alerts_as_data'; + +// The following fields were identified as technical field names but were not defined in the +// rule registry technical component template. We will leave these here for backwards +// compatibility but these consts should be moved to the plugin that uses them + const ALERT_RULE_THREAT_NAMESPACE = `${ALERT_RULE_NAMESPACE}.threat` as const; -const ECS_VERSION = 'ecs.version' as const; -const EVENT_ACTION = 'event.action' as const; -const EVENT_KIND = 'event.kind' as const; const EVENT_MODULE = 'event.module' as const; -const TAGS = 'tags' as const; -const TIMESTAMP = '@timestamp' as const; // Fields pertaining to the alert const ALERT_BUILDING_BLOCK_TYPE = `${ALERT_NAMESPACE}.building_block_type` as const; const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const; const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const; -const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const; -const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; -const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; -const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; -const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const; -const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const; -const ALERT_SUPPRESSION_META = `${ALERT_NAMESPACE}.suppression` as const; -const ALERT_SUPPRESSION_TERMS = `${ALERT_SUPPRESSION_META}.terms` as const; -const ALERT_SUPPRESSION_FIELD = `${ALERT_SUPPRESSION_TERMS}.field` as const; -const ALERT_SUPPRESSION_VALUE = `${ALERT_SUPPRESSION_TERMS}.value` as const; -const ALERT_SUPPRESSION_START = `${ALERT_SUPPRESSION_META}.start` as const; -const ALERT_SUPPRESSION_END = `${ALERT_SUPPRESSION_META}.end` as const; -const ALERT_SUPPRESSION_DOCS_COUNT = `${ALERT_SUPPRESSION_META}.docs_count` as const; - -// Fields pertaining to the cases associated with the alert -const ALERT_CASE_IDS = `${ALERT_NAMESPACE}.case_ids` as const; // Fields pertaining to the rule associated with the alert -const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const; -const ALERT_RULE_CREATED_AT = `${ALERT_RULE_NAMESPACE}.created_at` as const; -const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const; -const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const; -const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const; const ALERT_RULE_EXCEPTIONS_LIST = `${ALERT_RULE_NAMESPACE}.exceptions_list` as const; -const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const; -const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const; -const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const; const ALERT_RULE_NAMESPACE_FIELD = `${ALERT_RULE_NAMESPACE}.namespace` as const; -const ALERT_RULE_NOTE = `${ALERT_RULE_NAMESPACE}.note` as const; -const ALERT_RULE_REFERENCES = `${ALERT_RULE_NAMESPACE}.references` as const; -const ALERT_RULE_RULE_ID = `${ALERT_RULE_NAMESPACE}.rule_id` as const; -const ALERT_RULE_RULE_NAME_OVERRIDE = `${ALERT_RULE_NAMESPACE}.rule_name_override` as const; -const ALERT_RULE_TO = `${ALERT_RULE_NAMESPACE}.to` as const; -const ALERT_RULE_TYPE = `${ALERT_RULE_NAMESPACE}.type` as const; -const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const; -const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const; -const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const; // Fields pertaining to the threat tactic associated with the rule const ALERT_THREAT_FRAMEWORK = `${ALERT_RULE_THREAT_NAMESPACE}.framework` as const; @@ -186,36 +190,8 @@ export { ALERT_BUILDING_BLOCK_TYPE, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, - ALERT_INSTANCE_ID, - ALERT_RISK_SCORE, - ALERT_WORKFLOW_REASON, - ALERT_WORKFLOW_USER, - ALERT_CASE_IDS, - ALERT_RULE_AUTHOR, - ALERT_RULE_CREATED_AT, - ALERT_RULE_CREATED_BY, - ALERT_RULE_DESCRIPTION, - ALERT_RULE_ENABLED, ALERT_RULE_EXCEPTIONS_LIST, - ALERT_RULE_FROM, - ALERT_RULE_INTERVAL, - ALERT_RULE_LICENSE, ALERT_RULE_NAMESPACE_FIELD, - ALERT_RULE_NOTE, - ALERT_RULE_REFERENCES, - ALERT_RULE_RULE_ID, - ALERT_RULE_RULE_NAME_OVERRIDE, - ALERT_RULE_TO, - ALERT_RULE_TYPE, - ALERT_RULE_UPDATED_AT, - ALERT_RULE_UPDATED_BY, - ALERT_RULE_VERSION, - ALERT_SEVERITY, - ALERT_SYSTEM_STATUS, - ECS_VERSION, - EVENT_ACTION, - EVENT_KIND, - EVENT_MODULE, ALERT_THREAT_FRAMEWORK, ALERT_THREAT_TACTIC_ID, ALERT_THREAT_TACTIC_NAME, @@ -226,14 +202,7 @@ export { ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE, - ALERT_SUPPRESSION_TERMS, - ALERT_SUPPRESSION_FIELD, - ALERT_SUPPRESSION_VALUE, - ALERT_SUPPRESSION_START, - ALERT_SUPPRESSION_END, - ALERT_SUPPRESSION_DOCS_COUNT, - TAGS, - TIMESTAMP, + EVENT_MODULE, }; export type TechnicalRuleDataFieldName = ValuesType; diff --git a/packages/kbn-rule-data-utils/tsconfig.json b/packages/kbn-rule-data-utils/tsconfig.json index 5c94013fc2eaf..77352c4f44209 100644 --- a/packages/kbn-rule-data-utils/tsconfig.json +++ b/packages/kbn-rule-data-utils/tsconfig.json @@ -11,7 +11,7 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/es-query" + "@kbn/es-query", ], "exclude": [ "target/**/*", diff --git a/src/core/server/integration_tests/saved_objects/migrations/archives/1m_dummy_so.zip b/src/core/server/integration_tests/saved_objects/migrations/archives/1m_dummy_so.zip new file mode 100644 index 0000000000000..18b34d8b1ccb3 Binary files /dev/null and b/src/core/server/integration_tests/saved_objects/migrations/archives/1m_dummy_so.zip differ 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/batch_size_bytes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts index f2296d41d905e..17a628d32e1d8 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts @@ -118,7 +118,7 @@ describe('migration v2', () => { await root.preboot(); await root.setup(); await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715329 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` + `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715272 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` ); await retryAsync( @@ -131,7 +131,7 @@ describe('migration v2', () => { expect( records.find((rec) => rec.message.startsWith( - `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715329 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` + `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715272 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` ) ) ).toBeDefined(); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 7d9e4bae782e2..8aa1eb22fccbe 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -69,12 +69,12 @@ describe('checking migration metadata changes on all registered SO types', () => "canvas-element": "ec334dd45d14291db4d74197e0e42dfe06526868", "canvas-workpad": "ab0525bd5aa4dbad2d6fdb30e6a51bb475254751", "canvas-workpad-template": "c54f2a188a1d0bf18a6cebd9d6f28a7337d41bbf", - "cases": "74c00dfb25f4b109894971bd1090fce4a7c99490", - "cases-comments": "371662a8464e623f1f4f55a981cec78bec4a12f5", - "cases-configure": "25099c9e4bbb91e01e334848c605b4a5de5c9fce", - "cases-connector-mappings": "8de3b77dc6ae8ee62cce2b58a222471dfc3dbdad", + "cases": "1e86563e8364c69f86b77cb6f2933408dd5b827a", + "cases-comments": "69257ec55e8380fdb2ecbddc83e7c26d2ce2a351", + "cases-configure": "66d4c64d83b464f5166005b8ffa03b721fcaaf8b", + "cases-connector-mappings": "877bb4d52e9821e330622bd75fba799490ec6952", "cases-telemetry": "fdeddcef28c75d8c66422475a829e75d37f0b668", - "cases-user-actions": "cfd388d2ca27b3abfd3955dc41428fb229989921", + "cases-user-actions": "8ad74294b71edffa58fad7a40eea2388209477c9", "config": "97e16b8f5dc10c404fd3b201ef36bc6c3c63dc80", "config-global": "d9791e8f73edee884630e1cb6e4954ae513ce75e", "connector_token": "fb05ff5afdcb6e2f20c9c6513ff7a1ab12b66f36", @@ -126,7 +126,7 @@ describe('checking migration metadata changes on all registered SO types', () => "rules-settings": "9854495c3b54b16a6625fb250c35e5504da72266", "sample-data-telemetry": "c38daf1a49ed24f2a4fb091e6e1e833fccf19935", "search": "01bc42d635e9ea0588741c4c7a2bbd3feb3ac5dc", - "search-session": "5f40f6101fc2ec8ce5210d735ea2e00a87c02886", + "search-session": "58a44d14ec991739166b2ec28d718001ab0f4b28", "search-telemetry": "ab67ef721f294f28d5e10febbd20653347720188", "security-rule": "1ff82dfb2298c3caf6888fc3ef15c6bf7a628877", "security-solution-signals-migration": "c2db409c1857d330beb3d6fd188fa186f920302c", @@ -136,7 +136,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline-pinned-event": "96a43d59b9e2fc11f12255a0cb47ef0a3d83af4c", "space": "9542afcd6fd71558623c09151e453c5e84b4e5e1", "spaces-usage-stats": "084bd0f080f94fb5735d7f3cf12f13ec92f36bad", - "synthetics-monitor": "5d0a69fac9d6cfdacfa1962274344aecb596167a", + "synthetics-monitor": "96cc312bfa597022f83dfb3b5d1501e27a73e8d5", "synthetics-param": "9776c9b571d35f0d0397e8915e035ea1dc026db7", "synthetics-privates-locations": "7d032fc788905e32152029ae7ab3d6038c48ae44", "tag": "87f21f07df9cc37001b15a26e413c18f50d1fbfe", @@ -145,7 +145,6 @@ describe('checking migration metadata changes on all registered SO types', () => "ui-metric": "410a8ad28e0f44b161c960ff0ce950c712b17c52", "upgrade-assistant-ml-upgrade-operation": "d8816e5ce32649e7a3a43e2c406c632319ff84bb", "upgrade-assistant-reindex-operation": "09ac8ed9c9acf7e8ece8eafe47d7019ea1472144", - "upgrade-assistant-telemetry": "12bcbfc4e4ce64d2ca7c24f9acccd331a2bd2ab6", "uptime-dynamic-settings": "9a63ce80904a04be114749e426882dc3ff011137", "uptime-synthetics-api-key": "599319bedbfa287e8761e1ba49d536417a33fa13", "url": "2422b3cbe0af71f7a9c2e228e19a972e759c56d4", 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 7845ec72ab2ec..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, @@ -43,6 +43,7 @@ import { type DocumentsTransformFailed, type DocumentsTransformSuccess, MIGRATION_CLIENT_OPTIONS, + createBulkIndexOperationTuple, } from '@kbn/core-saved-objects-migration-server-internal'; const { startES } = createTestServers({ @@ -70,7 +71,11 @@ describe('migration actions', () => { indexName: 'existing_index_with_docs', mappings: { dynamic: true, - properties: {}, + properties: { + someProperty: { + type: 'integer', + }, + }, _meta: { migrationMappingPropertyHashes: { references: '7997cf5a56cc02bdc9c93361bde732b0', @@ -78,7 +83,7 @@ describe('migration actions', () => { }, }, })(); - const sourceDocs = [ + const docs = [ { _source: { title: 'doc 1' } }, { _source: { title: 'doc 2' } }, { _source: { title: 'doc 3' } }, @@ -88,7 +93,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - transformedDocs: sourceDocs, + operations: docs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })(); @@ -101,7 +106,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_write_block', - transformedDocs: sourceDocs, + operations: docs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })(); await setWriteBlock({ client, index: 'existing_index_with_write_block' })(); @@ -302,7 +307,7 @@ describe('migration actions', () => { const res = (await bulkOverwriteTransformedDocuments({ client, index: 'new_index_without_write_block', - transformedDocs: sourceDocs, + operations: sourceDocs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })()) as Either.Left; @@ -882,7 +887,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'reindex_target_4', - transformedDocs: sourceDocs, + operations: sourceDocs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })(); @@ -1441,7 +1446,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_without_mappings', - transformedDocs: sourceDocs, + operations: sourceDocs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })(); @@ -1485,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', + }, }, }, })(); @@ -1501,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', + }, }, }, })(); @@ -1518,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', }, }); @@ -1837,7 +1896,7 @@ describe('migration actions', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - transformedDocs: newDocs, + operations: newDocs.map(createBulkIndexOperationTuple), refresh: 'wait_for', }); @@ -1860,10 +1919,10 @@ describe('migration actions', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - transformedDocs: [ + operations: [ ...existingDocs, { _source: { title: 'doc 8' } } as unknown as SavedObjectsRawDoc, - ], + ].map(createBulkIndexOperationTuple), refresh: 'wait_for', }); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -1883,7 +1942,7 @@ describe('migration actions', () => { bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_write_block', - transformedDocs: newDocs, + operations: newDocs.map(createBulkIndexOperationTuple), refresh: 'wait_for', })() ).resolves.toMatchInlineSnapshot(` @@ -1906,7 +1965,7 @@ describe('migration actions', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - transformedDocs: newDocs, + operations: newDocs.map(createBulkIndexOperationTuple), }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.fixtures.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.fixtures.ts new file mode 100644 index 0000000000000..7d605cf116341 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.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/group3/active_delete.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts new file mode 100644 index 0000000000000..793ed9d100685 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts @@ -0,0 +1,391 @@ +/* + * Copyright 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 { 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'; + +describe('when upgrading to a new stack version', () => { + let esServer: TestElasticsearchUtils['es']; + let esClient: ElasticsearchClient; + + beforeAll(async () => { + esServer = await startElasticsearch(); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); + + describe('if the mappings match (diffMappings() === false)', () => { + describe('and discardUnknownObjects = true', () => { + let indexContents: SearchResponse<{ type: string }, Record>; + + beforeAll(async () => { + esClient = await createBaseline(); + + await clearLog(); + // remove the 'deprecated' type from the mappings, so that it is considered unknown + const { client, runMigrations } = await getNonDeprecatedMappingsMigrator({ + settings: { + migrations: { + discardUnknownObjects: nextMinor, + }, + }, + }); + + await runMigrations(); + + 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 -> CLEANUP_UNKNOWN_AND_EXCLUDED'); + // we gotta inform that we are deleting unknown documents too (discardUnknownObjects: true) + expect(logs).toMatch( + '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( + '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'); + }); + + 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 () => { + // remove the 'deprecated' type from the mappings, so that it is considered unknown + const { runMigrations } = await getNonDeprecatedMappingsMigrator(); + + 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 -> 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( + '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.'); + + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + expect(indexContents.hits.hits.length).toEqual(8); + }); + }); + }); + + 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(); + + 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 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( + '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.'); + }); + + 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( + '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.'); + + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + + expect(indexContents.hits.hits.length).toEqual(8); + }); + }); + }); + + 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: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + beforeEach(async () => { + await clearLog(); + }); + + it('the migrator does not skip reindexing', async () => { + const { client, runMigrations } = await getIncompatibleMappingsMigrator(); + + 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 -> 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: 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 + 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/active_delete_multiple_instances.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete_multiple_instances.test.ts new file mode 100644 index 0000000000000..57e4844ef3182 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete_multiple_instances.test.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import fs from 'fs/promises'; +import { SemVer } from 'semver'; +import { Env } from '@kbn/config'; +import { getEnvOptions } from '@kbn/config-mocks'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import { + defaultLogFilePath, + getEsClient, + getKibanaMigratorTestKit, + startElasticsearch, +} from '../kibana_migrator_test_kit'; +import { baselineTypes } from './active_delete.fixtures'; +import { delay } from '../test_utils'; +import { createBaselineArchive } from '../kibana_migrator_archive_utils'; + +const PARALLEL_MIGRATORS = 6; +const DOCUMENTS_PER_TYPE = 250000; + +const kibanaIndex = '.kibana_migrator_tests'; +const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; +const nextMinor = new SemVer(currentVersion).inc('minor').format(); + +const dataArchive = Path.join(__dirname, '..', 'archives', '1m_dummy_so.zip'); + +jest.setTimeout(24 * 3600 * 100); + +describe('multiple migrator instances running in parallel', () => { + it.skip('enable and focus this test (it.skip => fit), and run it, in order to create a baseline archive', async () => { + // generate DOCUMENTS_PER_TYPE documents of each type + const documents: SavedObjectsBulkCreateObject[] = ['server', 'basic', 'deprecated', 'complex'] + .map((type) => + new Array(DOCUMENTS_PER_TYPE).fill(true).map((_, index) => ({ + type, + attributes: { + name: `${type}-${++index}`, + ...(type === 'complex' && { value: index }), + }, + })) + ) + .flat(); + + await createBaselineArchive({ kibanaIndex, types: baselineTypes, documents, dataArchive }); + }); + + describe('when upgrading to a new stack version with matching mappings', () => { + let esServer: TestElasticsearchUtils['es']; + let esClient: ElasticsearchClient; + beforeAll(async () => { + esServer = await startElasticsearch({ dataArchive }); + esClient = await getEsClient(); + await fs.unlink(defaultLogFilePath).catch(() => {}); + + for (let i = 0; i < PARALLEL_MIGRATORS; ++i) { + await fs.unlink(Path.join(__dirname, `active_delete_instance_${i}.log`)).catch(() => {}); + } + }); + + it('will actively delete and successfully complete migration', async () => { + const startTime = Date.now(); + const types = baselineTypes + .filter((type) => type.name !== 'deprecated') + .map((type) => { + if (type.name !== 'complex') { + return type; + } + + return { + ...type, + excludeOnUpgrade: () => { + return { + bool: { + must: [ + { term: { type: 'complex' } }, + { range: { 'complex.value': { lte: 125000 } } }, + ], + }, + }; + }, + }; + }); + + const beforeCleanup = await getAggregatedTypesCount(); + expect(beforeCleanup.server).toEqual(DOCUMENTS_PER_TYPE); + expect(beforeCleanup.basic).toEqual(DOCUMENTS_PER_TYPE); + expect(beforeCleanup.deprecated).toEqual(DOCUMENTS_PER_TYPE); + expect(beforeCleanup.complex).toEqual(DOCUMENTS_PER_TYPE); + + const testKits = await Promise.all( + new Array(PARALLEL_MIGRATORS) + .fill({ + settings: { + migrations: { + discardUnknownObjects: nextMinor, + }, + }, + kibanaIndex, + types, + kibanaVersion: nextMinor, + }) + .map((config, index) => + getKibanaMigratorTestKit({ + ...config, + logFilePath: Path.join(__dirname, `active_delete_instance_${index}.log`), + }) + ) + ); + + const results = await Promise.all(testKits.map((testKit) => testKit.runMigrations())); + expect(results.flat().every((result) => result.status === 'migrated')).toEqual(true); + + for (let i = 0; i < PARALLEL_MIGRATORS; ++i) { + const logs = await fs.readFile( + Path.join(__dirname, `active_delete_instance_${i}.log`), + 'utf-8' + ); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + expect(logs).toMatch('Migration completed'); + } + + const endTime = Date.now(); + // eslint-disable-next-line no-console + console.debug(`Migration took: ${(endTime - startTime) / 1000} seconds`); + + // After cleanup + const afterCleanup = await getAggregatedTypesCount(); + expect(afterCleanup.server).not.toBeDefined(); // 'server' is part of the REMOVED_TYPES + expect(afterCleanup.basic).toEqual(DOCUMENTS_PER_TYPE); // we keep 'basic' SOs + expect(afterCleanup.deprecated).not.toBeDefined(); // 'deprecated' is no longer present in nextMinor's mappings + expect(afterCleanup.complex).toEqual(DOCUMENTS_PER_TYPE / 2); // we excludeFromUpgrade half of them with a hook + }); + + afterAll(async () => { + // await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esServer?.stop(); + await delay(10); + }); + + const getAggregatedTypesCount = async () => { + await esClient.indices.refresh(); + const response = await esClient.search({ + index: kibanaIndex, + _source: false, + aggs: { + typesAggregation: { + terms: { + // assign type __UNKNOWN__ to those documents that don't define one + missing: '__UNKNOWN__', + field: 'type', + size: 10, + }, + aggs: { + docs: { + top_hits: { + size: 2, + _source: { + excludes: ['*'], + }, + }, + }, + }, + }, + }, + }); + + return (response.aggregations!.typesAggregation.buckets as unknown as any).reduce( + (acc: any, current: any) => { + acc[current.key] = current.doc_count; + return acc; + }, + {} + ); + }; + }); +}); 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 a6060b166335f..5354a958e8cb7 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,115 +5,137 @@ * 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; +describe('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 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.'); - const nextPatch = new SemVer(currentVersion).inc('patch').format(); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS'); + expect(logs).not.toMatch('REINDEX'); + }); + }); - logs = await fs.readFile(logFilePath, 'utf-8'); + 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(); - expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> 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 -> 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.'); - 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('CLEANUP_UNKNOWN_AND_EXCLUDED'); + expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION'); + }); + }); - // 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(() => {}); + 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.'); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); + 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'); - logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - expect(logs).not.toMatch('INIT -> PREPARE_COMPATIBLE_MIGRATION'); + // 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_archive_utils.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_archive_utils.ts new file mode 100644 index 0000000000000..88342c1a66ac3 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_archive_utils.ts @@ -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 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. + */ +/* eslint-disable no-console */ + +import Path from 'path'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +const execPromise = promisify(exec); + +import { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { getKibanaMigratorTestKit, startElasticsearch } from './kibana_migrator_test_kit'; +import { delay } from './test_utils'; + +const DEFAULT_BATCH_SIZE = 100000; + +interface CreateBaselineArchiveParams { + kibanaIndex: string; + types: Array>; + documents: SavedObjectsBulkCreateObject[]; + batchSize?: number; + esBaseFolder?: string; + dataArchive: string; +} + +export const createBaselineArchive = async ({ + types, + documents, + kibanaIndex, + batchSize = DEFAULT_BATCH_SIZE, + esBaseFolder = Path.join(__dirname, `target`), + dataArchive, +}: CreateBaselineArchiveParams) => { + const startTime = Date.now(); + const esServer = await startElasticsearch({ basePath: esBaseFolder }); + + const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ + kibanaIndex, + types, + }); + + await runMigrations(); + + const batches = Math.ceil(documents.length / batchSize); + + for (let i = 0; i < batches; ++i) { + console.log(`Indexing up to ${batchSize} docs (batch ${i + 1} of ${batches})`); + await savedObjectsRepository.bulkCreate(documents.slice(batchSize * i, batchSize * (i + 1)), { + refresh: 'wait_for', + }); + } + + await compressBaselineArchive(esBaseFolder, dataArchive); + console.log(`Archive created in: ${(Date.now() - startTime) / 1000} seconds`, dataArchive); + await delay(200); + await esServer.stop(); + // await fs.rm(esBaseFolder, { recursive: true }); +}; + +const compressBaselineArchive = async (esFolder: string, archiveFile: string) => { + const dataFolder = Path.join(esFolder, 'es-test-cluster'); + const cmd = `cd ${dataFolder} && zip -r ${archiveFile} data -x ".DS_Store" -x "__MACOSX"`; + await execPromise(cmd); +}; 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 new file mode 100644 index 0000000000000..f6760aa3264fc --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -0,0 +1,390 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import fs from 'fs/promises'; +import { SemVer } from 'semver'; + +import { defaultsDeep } from 'lodash'; +import { BehaviorSubject, firstValueFrom, map } from 'rxjs'; +import { ConfigService, Env } from '@kbn/config'; +import { getEnvOptions } from '@kbn/config-mocks'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { KibanaMigrator } from '@kbn/core-saved-objects-migration-server-internal'; + +import { + SavedObjectConfig, + type SavedObjectsConfigType, + type SavedObjectsMigrationConfigType, + SavedObjectTypeRegistry, + type IKibanaMigrator, + type MigrationResult, +} from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; +import { + ElasticsearchConfig, + type ElasticsearchConfigType, +} from '@kbn/core-elasticsearch-server-internal'; +import { AgentManager, configureClient } from '@kbn/core-elasticsearch-client-server-internal'; +import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server-internal'; + +import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { esTestConfig, kibanaServerTestUser } from '@kbn/test'; +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 type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { getDocLinks, getDocLinksMeta } from '@kbn/doc-links'; +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 +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; + kibanaVersion?: string; + logFilePath?: string; +} + +export interface KibanaMigratorTestKitParams { + kibanaIndex?: string; + kibanaVersion?: string; + kibanaBranch?: string; + settings?: Record; + types?: Array>; + logFilePath?: string; +} + +export interface KibanaMigratorTestKit { + client: ElasticsearchClient; + migrator: IKibanaMigrator; + runMigrations: (rerun?: boolean) => Promise; + typeRegistry: ISavedObjectTypeRegistry; + savedObjectsRepository: ISavedObjectsRepository; +} + +export const startElasticsearch = async ({ + basePath, + dataArchive, +}: { + basePath?: string; + dataArchive?: string; +} = {}) => { + const { startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + es: { + license: 'basic', + basePath, + dataArchive, + }, + }, + }); + return await startES(); +}; + +export const getEsClient = async ({ + settings = {}, + kibanaVersion = currentVersion, + logFilePath = defaultLogFilePath, +}: GetEsClientParams = {}) => { + const loggingSystem = new LoggingSystem(); + const loggerFactory = loggingSystem.asLoggerFactory(); + + const configService = getConfigService(settings, loggerFactory, logFilePath); + + // configure logging system + const loggingConf = await firstValueFrom(configService.atPath('logging')); + loggingSystem.upgrade(loggingConf); + + return await getElasticsearchClient(configService, loggerFactory, kibanaVersion); +}; + +export const getKibanaMigratorTestKit = async ({ + settings = {}, + kibanaIndex = defaultKibanaIndex, + kibanaVersion = currentVersion, + kibanaBranch = currentBranch, + types = [], + logFilePath = defaultLogFilePath, +}: KibanaMigratorTestKitParams = {}): Promise => { + const loggingSystem = new LoggingSystem(); + const loggerFactory = loggingSystem.asLoggerFactory(); + + const configService = getConfigService(settings, loggerFactory, logFilePath); + + // configure logging system + const loggingConf = await firstValueFrom(configService.atPath('logging')); + loggingSystem.upgrade(loggingConf); + + const client = await getElasticsearchClient(configService, loggerFactory, kibanaVersion); + + const typeRegistry = new SavedObjectTypeRegistry(); + + // types must be registered before instantiating the migrator + registerTypes(typeRegistry, types); + + const migrator = await getMigrator( + configService, + client, + typeRegistry, + loggerFactory, + kibanaIndex, + kibanaVersion, + kibanaBranch + ); + + const runMigrations = async (rerun?: boolean) => { + migrator.prepareMigrations(); + return await migrator.runMigrations({ rerun }); + }; + + const savedObjectsRepository = SavedObjectsRepository.createRepository( + migrator, + typeRegistry, + kibanaIndex, + client, + loggerFactory.get('saved_objects') + ); + + return { + client, + migrator, + runMigrations, + typeRegistry, + savedObjectsRepository, + }; +}; + +const getConfigService = ( + settings: Record, + loggerFactory: LoggerFactory, + logFilePath: string +) => { + // Define some basic default kibana settings + const DEFAULTS_SETTINGS = { + server: { + autoListen: true, + // Use the ephemeral port to make sure that tests use the first available + // port and aren't affected by the timing issues in test environment. + port: 0, + xsrf: { disableProtection: true }, + }, + elasticsearch: { + hosts: [esTestConfig.getUrl()], + username: kibanaServerTestUser.username, + password: kibanaServerTestUser.password, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + level: 'info', + appenders: ['file'], + }, + ], + }, + plugins: {}, + migrations: { skip: false }, + }; + + const rawConfigProvider = { + getConfig$: () => new BehaviorSubject(defaultsDeep({}, settings, DEFAULTS_SETTINGS)), + }; + + const configService = new ConfigService(rawConfigProvider, env, loggerFactory); + registerServiceConfig(configService); + return configService; +}; + +const getElasticsearchClient = async ( + configService: ConfigService, + loggerFactory: LoggerFactory, + kibanaVersion: string +) => { + const esClientConfig = await firstValueFrom( + configService + .atPath('elasticsearch') + .pipe(map((rawConfig) => new ElasticsearchConfig(rawConfig))) + ); + + return configureClient(esClientConfig, { + logger: loggerFactory.get('elasticsearch'), + type: 'data', + agentFactoryProvider: new AgentManager(), + kibanaVersion, + }); +}; + +const getMigrator = async ( + configService: ConfigService, + client: ElasticsearchClient, + typeRegistry: ISavedObjectTypeRegistry, + loggerFactory: LoggerFactory, + kibanaIndex: string, + kibanaVersion: string, + kibanaBranch: string +) => { + const savedObjectsConf = await firstValueFrom( + configService.atPath('savedObjects') + ); + const savedObjectsMigrationConf = await firstValueFrom( + configService.atPath('migrations') + ); + const soConfig = new SavedObjectConfig(savedObjectsConf, savedObjectsMigrationConf); + + const docLinks: DocLinksServiceStart = { + ...getDocLinksMeta({ kibanaBranch }), + links: getDocLinks({ kibanaBranch }), + }; + + return new KibanaMigrator({ + client, + typeRegistry, + kibanaIndex, + soMigrationsConfig: soConfig.migration, + kibanaVersion, + logger: loggerFactory.get('savedobjects-service'), + docLinks, + waitForMigrationCompletion: false, // ensure we have an active role in the migration + }); +}; + +const registerTypes = ( + typeRegistry: SavedObjectTypeRegistry, + types?: Array> +) => { + (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/core/tsconfig.json b/src/core/tsconfig.json index 1791e14334243..d1c69ad2d1e49 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -148,6 +148,7 @@ "@kbn/core-lifecycle-browser", "@kbn/core-custom-branding-browser", "@kbn/core-custom-branding-server", + "@kbn/core-elasticsearch-client-server-internal", ], "exclude": [ "target/**/*", diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index dd0600b71d2b2..e45c89bf69b7a 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -84,6 +84,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.4.0': ['Elastic License 2.0'], - '@elastic/eui@75.1.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@75.1.2': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/dev/performance/run_performance_cli.ts b/src/dev/performance/run_performance_cli.ts index daec946f8962b..3539ada07e405 100644 --- a/src/dev/performance/run_performance_cli.ts +++ b/src/dev/performance/run_performance_cli.ts @@ -103,7 +103,7 @@ run( process.stdout.write(`--- Starting ES\n`); await procRunner.run('es', { cmd: 'node', - args: ['scripts/es', 'snapshot'], + args: ['scripts/es', 'snapshot', '--license=trial'], cwd: REPO_ROOT, wait: /kbn\/es setup complete/, }); diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index fc80a35a2def8..47ebefcc9c8c7 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,7 +18,7 @@ export const storybookAliases = { language_documentation_popover: 'packages/kbn-language-documentation-popover/.storybook', chart_icons: 'packages/kbn-chart-icons/.storybook', content_management: 'packages/content-management/.storybook', - content_management_plugin: 'src/plugins/content_management/.storybook', + content_management_examples: 'examples/content_management_examples/.storybook', controls: 'src/plugins/controls/storybook', custom_integrations: 'src/plugins/custom_integrations/storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap index c91e491887a99..1a566571b4d6c 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap @@ -1267,7 +1267,7 @@ exports[`PartitionVisComponent should render correct structure for multi-metric "fillColor": [Function], }, "showAccessor": [Function], - "sortPredicate": undefined, + "sortPredicate": [Function], }, ] } diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts index 225f405bac2ca..06fa1d9aae83b 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts @@ -10,6 +10,15 @@ import type { PaletteOutput, PaletteDefinition } from '@kbn/coloring'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { Datatable } from '@kbn/expressions-plugin/common'; import { byDataColorPaletteMap } from './get_color'; +import { ShapeTreeNode } from '@elastic/charts'; +import type { SeriesLayer } from '@kbn/coloring'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { getColor } from './get_color'; +import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks'; +import { generateFormatters } from '../formatters'; +import { ChartTypes } from '../../../common/types'; describe('#byDataColorPaletteMap', () => { let datatable: Datatable; @@ -88,3 +97,284 @@ describe('#byDataColorPaletteMap', () => { ); }); }); + +describe('getColor', () => { + const visData = createMockVisData(); + const buckets = createMockBucketColumns(); + const visParams = createMockPieParams(); + const colors = ['color1', 'color2', 'color3', 'color4']; + const dataMock = dataPluginMock.createStartContract(); + interface RangeProps { + gte: number; + lt: number; + } + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const formatters = generateFormatters(visData, defaultFormatter); + + dataMock.fieldFormats = { + deserialize: jest.fn(() => ({ + convert: jest.fn((s: RangeProps) => { + return `≥ ${s.gte} and < ${s.lt}`; + }), + })), + } as unknown as DataPublicPluginStart['fieldFormats']; + + const getPaletteRegistry = () => { + const mockPalette1: jest.Mocked = { + id: 'default', + title: 'My Palette', + getCategoricalColor: jest.fn((layer: SeriesLayer[]) => colors[layer[0].rankAtDepth]), + getCategoricalColors: jest.fn((num: number) => colors), + toExpression: jest.fn(() => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'system_palette', + arguments: { + name: ['default'], + }, + }, + ], + })), + }; + + return { + get: () => mockPalette1, + getAll: () => [mockPalette1], + }; + }; + it('should return the correct color based on the parent sortIndex', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + false, + {}, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual(colors[0]); + }); + + it('slices with the same label should have the same color for small multiples', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + {}, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual('color3'); + }); + it('returns the overwriteColor if exists', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + { 'ES-Air': '#000028' }, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual('#000028'); + }); + + it('returns the overwriteColor for older visualizations with formatted values', () => { + const d = { + dataName: { + gte: 1000, + lt: 2000, + }, + depth: 1, + sortIndex: 0, + parent: { + children: [ + [ + { + gte: 1000, + lt: 2000, + }, + ], + [ + { + gte: 2000, + lt: 3000, + }, + ], + ], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const visParamsNew = { + ...visParams, + distinctColors: true, + }; + const column = { + ...visData.columns[0], + format: { + id: 'range', + params: { + id: 'number', + }, + }, + }; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + { '≥ 1000 and < 2000': '#3F6833' }, + buckets, + visData.rows, + visParamsNew, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + column, + formatters + ); + expect(color).toEqual('#3F6833'); + }); + + it('should only pass the second layer for mosaic', () => { + const d = { + dataName: 'Second level 1', + depth: 2, + sortIndex: 0, + parent: { + children: [['Second level 1'], ['Second level 2']], + depth: 1, + sortIndex: 0, + parent: { + children: [['First level']], + depth: 0, + sortIndex: 0, + }, + }, + } as unknown as ShapeTreeNode; + const registry = getPaletteRegistry(); + getColor( + ChartTypes.MOSAIC, + d, + 1, + true, + {}, + buckets, + visData.rows, + visParams, + registry, + undefined, + true, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( + [expect.objectContaining({ name: 'Second level 1' })], + expect.anything(), + expect.anything() + ); + }); + + it('should only pass the first layer for treemap', () => { + const d = { + dataName: 'Second level 1', + depth: 2, + sortIndex: 0, + parent: { + children: [['Second level 1'], ['Second level 2']], + depth: 1, + sortIndex: 0, + parent: { + children: [['First level']], + depth: 0, + sortIndex: 0, + }, + }, + } as unknown as ShapeTreeNode; + const registry = getPaletteRegistry(); + getColor( + ChartTypes.TREEMAP, + d, + 1, + true, + {}, + buckets, + visData.rows, + visParams, + registry, + undefined, + true, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( + [expect.objectContaining({ name: 'First level' })], + expect.anything(), + expect.anything() + ); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts index d96ada51ba47a..7300aac06329f 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts @@ -5,294 +5,110 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { ShapeTreeNode } from '@elastic/charts'; -import type { PaletteDefinition, SeriesLayer } from '@kbn/coloring'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { getColor } from './get_color'; -import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks'; -import { generateFormatters } from '../formatters'; -import { ChartTypes } from '../../../common/types'; - -const visData = createMockVisData(); -const buckets = createMockBucketColumns(); -const visParams = createMockPieParams(); -const colors = ['color1', 'color2', 'color3', 'color4']; -const dataMock = dataPluginMock.createStartContract(); -interface RangeProps { - gte: number; - lt: number; -} -const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); -const formatters = generateFormatters(visData, defaultFormatter); -dataMock.fieldFormats = { - deserialize: jest.fn(() => ({ - convert: jest.fn((s: RangeProps) => { - return `≥ ${s.gte} and < ${s.lt}`; - }), - })), -} as unknown as DataPublicPluginStart['fieldFormats']; +import { ArrayEntry, ArrayNode } from '@elastic/charts'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import { BucketColumns, ChartTypes } from '../../../common/types'; +import { createMockPieParams, createMockVisData } from '../../mocks'; +import { getPaletteRegistry } from '../../__mocks__/palettes'; +import { getLayers } from './get_layers'; -export const getPaletteRegistry = () => { - const mockPalette1: jest.Mocked = { - id: 'default', - title: 'My Palette', - getCategoricalColor: jest.fn((layer: SeriesLayer[]) => colors[layer[0].rankAtDepth]), - getCategoricalColors: jest.fn((num: number) => colors), - toExpression: jest.fn(() => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'system_palette', - arguments: { - name: ['default'], +describe('getLayers', () => { + it('preserves slice order for multi-metric layer', () => { + const visData = createMockVisData(); + const columns: BucketColumns[] = [ + { + id: 'col-0-0', + name: 'Normal column', + meta: { type: 'murmur3' }, + }, + { + id: 'col-0-0', + name: 'multi-metric column', + meta: { + type: 'number', + sourceParams: { + consolidatedMetricsColumn: true, }, }, - ], - })), - }; - - return { - get: () => mockPalette1, - getAll: () => [mockPalette1], - }; -}; - -describe('computeColor', () => { - it('should return the correct color based on the parent sortIndex', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, }, - } as unknown as ShapeTreeNode; - const color = getColor( + ]; + const visParams = createMockPieParams(); + const layers = getLayers( ChartTypes.PIE, - d, - 0, - false, - {}, - buckets, - visData.rows, + columns, visParams, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(color).toEqual(colors[0]); - }); - - it('slices with the same label should have the same color for small multiples', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, + visData, {}, - buckets, - visData.rows, - visParams, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(color).toEqual('color3'); - }); - it('returns the overwriteColor if exists', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, - { 'ES-Air': '#000028' }, - buckets, - visData.rows, - visParams, + [], getPaletteRegistry(), - { getColor: () => undefined }, - false, + {}, + fieldFormatsMock, false, - dataMock.fieldFormats, - visData.columns[0], - formatters + false ); - expect(color).toEqual('#000028'); - }); - it('returns the overwriteColor for older visualizations with formatted values', () => { - const d = { - dataName: { - gte: 1000, - lt: 2000, - }, - depth: 1, - sortIndex: 0, - parent: { - children: [ - [ - { - gte: 1000, - lt: 2000, - }, - ], - [ - { - gte: 2000, - lt: 3000, - }, - ], - ], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const visParamsNew = { - ...visParams, - distinctColors: true, - }; - const column = { - ...visData.columns[0], - format: { - id: 'range', - params: { - id: 'number', - }, - }, - }; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, - { '≥ 1000 and < 2000': '#3F6833' }, - buckets, - visData.rows, - visParamsNew, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - column, - formatters - ); - expect(color).toEqual('#3F6833'); - }); + expect(layers[0].sortPredicate).toBeUndefined(); + expect(layers[1].sortPredicate).toBeDefined(); - it('should only pass the second layer for mosaic', () => { - const d = { - dataName: 'Second level 1', - depth: 2, - sortIndex: 0, - parent: { - children: [['Second level 1'], ['Second level 2']], - depth: 1, - sortIndex: 0, - parent: { - children: [['First level']], - depth: 0, - sortIndex: 0, - }, - }, - } as unknown as ShapeTreeNode; - const registry = getPaletteRegistry(); - getColor( - ChartTypes.MOSAIC, - d, - 1, - true, - {}, - buckets, - visData.rows, - visParams, - registry, - undefined, - true, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( - [expect.objectContaining({ name: 'Second level 1' })], - expect.anything(), - expect.anything() - ); - }); + const testNodes: ArrayEntry[] = [ + [ + '', + { + value: 2, + inputIndex: [3], + } as ArrayNode, + ], + [ + '', + { + value: 1, + inputIndex: [2], + } as ArrayNode, + ], - it('should only pass the first layer for treemap', () => { - const d = { - dataName: 'Second level 1', - depth: 2, - sortIndex: 0, - parent: { - children: [['Second level 1'], ['Second level 2']], - depth: 1, - sortIndex: 0, - parent: { - children: [['First level']], - depth: 0, - sortIndex: 0, - }, - }, - } as unknown as ShapeTreeNode; - const registry = getPaletteRegistry(); - getColor( - ChartTypes.TREEMAP, - d, - 1, - true, - {}, - buckets, - visData.rows, - visParams, - registry, - undefined, - true, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( - [expect.objectContaining({ name: 'First level' })], - expect.anything(), - expect.anything() - ); + [ + '', + { + value: 3, + inputIndex: [1], + } as ArrayNode, + ], + ]; + + const predicate = layers[1].sortPredicate!; + testNodes.sort(predicate); + + expect(testNodes).toMatchInlineSnapshot(` + Array [ + Array [ + "", + Object { + "inputIndex": Array [ + 1, + ], + "value": 3, + }, + ], + Array [ + "", + Object { + "inputIndex": Array [ + 2, + ], + "value": 1, + }, + ], + Array [ + "", + Object { + "inputIndex": Array [ + 3, + ], + "value": 2, + }, + ], + ] + `); }); }); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts index 646a0e4b45d2e..93a9cc2b6c7d6 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts @@ -12,7 +12,7 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { Datatable, DatatableRow } from '@kbn/expressions-plugin/public'; import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types'; -import { sortPredicateByType } from './sort_predicate'; +import { sortPredicateByType, sortPredicateSaveSourceOrder } from './sort_predicate'; import { byDataColorPaletteMap, getColor } from './get_color'; import { getNodeLabel } from './get_node_labels'; @@ -52,7 +52,7 @@ export const getLayers = ( ); } - const sortPredicate = sortPredicateByType(chartType, visParams, visData, columns); + const sortPredicateForType = sortPredicateByType(chartType, visParams, visData, columns); return columns.map((col, layerIndex) => { return { groupByRollup: (d: Datum) => (col.id ? d[col.id] ?? EMPTY_SLICE : col.name), @@ -62,7 +62,9 @@ export const getLayers = ( layerIndex === 0 && chartType === ChartTypes.MOSAIC ? { ...fillLabel, minFontSize: 14, maxFontSize: 14, clipText: true } : fillLabel, - sortPredicate, + sortPredicate: col.meta?.sourceParams?.consolidatedMetricsColumn + ? sortPredicateSaveSourceOrder() + : sortPredicateForType, shape: { fillColor: (d) => getColor( diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts index 0e4564a9bcfe5..b5992c3b600bf 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts @@ -30,7 +30,7 @@ export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) => {} ); -const sortPredicateSaveSourceOrder: SortPredicatePureFn = +export const sortPredicateSaveSourceOrder: SortPredicatePureFn = () => ([, node1], [, node2]) => { const [index1] = node1.inputIndex ?? []; diff --git a/src/plugins/console/server/services/spec_definitions_service.ts b/src/plugins/console/server/services/spec_definitions_service.ts index b5515ed1308fa..b978c3c326159 100644 --- a/src/plugins/console/server/services/spec_definitions_service.ts +++ b/src/plugins/console/server/services/spec_definitions_service.ts @@ -9,6 +9,7 @@ import _, { merge } from 'lodash'; import globby from 'globby'; import { basename, join, resolve } from 'path'; +import normalizePath from 'normalize-path'; import { readFileSync } from 'fs'; import { jsSpecLoaders } from '../lib'; @@ -115,8 +116,9 @@ export class SpecDefinitionsService { } private loadJSONSpecInDir(dirname: string) { - const generatedFiles = globby.sync(join(dirname, 'generated', '*.json')); - const overrideFiles = globby.sync(join(dirname, 'overrides', '*.json')); + // we need to normalize paths otherwise they don't work on windows, see https://github.com/elastic/kibana/issues/151032 + const generatedFiles = globby.sync(normalizePath(join(dirname, 'generated', '*.json'))); + const overrideFiles = globby.sync(normalizePath(join(dirname, 'overrides', '*.json'))); return generatedFiles.reduce((acc, file) => { const overrideFile = overrideFiles.find((f) => basename(f) === basename(file)); diff --git a/src/plugins/content_management/common/constants.ts b/src/plugins/content_management/common/constants.ts index 5c35a41577b82..90298e1729d89 100644 --- a/src/plugins/content_management/common/constants.ts +++ b/src/plugins/content_management/common/constants.ts @@ -8,4 +8,4 @@ export const PLUGIN_ID = 'contentManagement'; -export const API_ENDPOINT = '/api/content_management'; +export const API_ENDPOINT = '/api/content_management/rpc'; diff --git a/src/plugins/content_management/common/index.ts b/src/plugins/content_management/common/index.ts index 0bc6549a1681b..ac944daf3571b 100644 --- a/src/plugins/content_management/common/index.ts +++ b/src/plugins/content_management/common/index.ts @@ -19,4 +19,7 @@ export type { SearchIn, } from './rpc'; -export { procedureNames, schemas as rpcSchemas } from './rpc'; +export { procedureNames } from './rpc/constants'; + +// intentionally not exporting schemas to not include @kbn/schema in the public bundle +// export { schemas as rpcSchemas } from './rpc'; diff --git a/src/plugins/content_management/common/schemas.ts b/src/plugins/content_management/common/schemas.ts new file mode 100644 index 0000000000000..172e0f4b28662 --- /dev/null +++ b/src/plugins/content_management/common/schemas.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +// exporting schemas separately from the index.ts file to not include @kbn/schema in the public bundle +// should be only used server-side or in jest tests +export { schemas as rpcSchemas } from './rpc'; diff --git a/src/plugins/content_management/public/index.ts b/src/plugins/content_management/public/index.ts index 0bf75e9d1087a..2687287a36c2f 100644 --- a/src/plugins/content_management/public/index.ts +++ b/src/plugins/content_management/public/index.ts @@ -7,6 +7,18 @@ */ import { ContentManagementPlugin } from './plugin'; +export type { CrudClient } from './crud_client'; +export { + ContentClientProvider, + ContentClient, + useCreateContentMutation, + useUpdateContentMutation, + useDeleteContentMutation, + useSearchContentQuery, + useGetContentQuery, + useContentClient, + type QueryOptions, +} from './content_client'; export function plugin() { return new ContentManagementPlugin(); diff --git a/src/plugins/content_management/public/plugin.ts b/src/plugins/content_management/public/plugin.ts index d82e2fadfb3e6..b2798e4224384 100644 --- a/src/plugins/content_management/public/plugin.ts +++ b/src/plugins/content_management/public/plugin.ts @@ -13,8 +13,9 @@ import { SetupDependencies, StartDependencies, } from './types'; -import type { ContentClient } from './content_client'; -import type { ContentTypeRegistry } from './registry'; +import { ContentClient } from './content_client'; +import { ContentTypeRegistry } from './registry'; +import { RpcClient } from './rpc_client'; export class ContentManagementPlugin implements @@ -26,20 +27,17 @@ export class ContentManagementPlugin > { public setup(core: CoreSetup, deps: SetupDependencies) { - // don't actually expose the client and the registry until it is used to avoid increasing bundle size return { registry: {} as ContentTypeRegistry, }; } public start(core: CoreStart, deps: StartDependencies) { - // don't actually expose the client and the registry until it is used to avoid increasing bundle size - // const rpcClient = new RpcClient(core.http); - // const contentTypeRegistry = new ContentTypeRegistry(); - // const contentClient = new ContentClient( - // (contentType) => contentTypeRegistry.get(contentType)?.crud() ?? rpcClient - // ); - // return { client: contentClient, registry: contentTypeRegistry }; - return { client: {} as ContentClient, registry: {} as ContentTypeRegistry }; + const rpcClient = new RpcClient(core.http); + const contentTypeRegistry = new ContentTypeRegistry(); + const contentClient = new ContentClient( + (contentType) => contentTypeRegistry.get(contentType)?.crud ?? rpcClient + ); + return { client: contentClient, registry: contentTypeRegistry }; } } diff --git a/src/plugins/content_management/public/rpc_client/rpc_client.test.ts b/src/plugins/content_management/public/rpc_client/rpc_client.test.ts index 3a76b355795ed..5e75b45be86ec 100644 --- a/src/plugins/content_management/public/rpc_client/rpc_client.test.ts +++ b/src/plugins/content_management/public/rpc_client/rpc_client.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { procedureNames, API_ENDPOINT } from '../../common'; +import { API_ENDPOINT, procedureNames } from '../../common'; import { RpcClient } from './rpc_client'; diff --git a/src/plugins/content_management/public/rpc_client/rpc_client.ts b/src/plugins/content_management/public/rpc_client/rpc_client.ts index 2d3e6d1ed1cce..9ec27342d45f2 100644 --- a/src/plugins/content_management/public/rpc_client/rpc_client.ts +++ b/src/plugins/content_management/public/rpc_client/rpc_client.ts @@ -18,36 +18,44 @@ import type { ProcedureName, } from '../../common'; import type { CrudClient } from '../crud_client/crud_client'; +import type { + GetResponse, + BulkGetResponse, + CreateItemResponse, + DeleteItemResponse, + UpdateItemResponse, + SearchResponse, +} from '../../server/core/crud'; export class RpcClient implements CrudClient { constructor(private http: { post: HttpSetup['post'] }) {} public get(input: I): Promise { - return this.sendMessage('get', input); + return this.sendMessage>('get', input).then((r) => r.item); } public bulkGet(input: I): Promise { - return this.sendMessage('bulkGet', input); + return this.sendMessage>('bulkGet', input).then((r) => r.items); } public create(input: I): Promise { - return this.sendMessage('create', input); + return this.sendMessage>('create', input).then((r) => r.result); } public update(input: I): Promise { - return this.sendMessage('update', input); + return this.sendMessage>('update', input).then((r) => r.result); } public delete(input: I): Promise { - return this.sendMessage('delete', input); + return this.sendMessage('delete', input).then((r) => r.result); } public search(input: I): Promise { - return this.sendMessage('search', input); + return this.sendMessage('search', input).then((r) => r.result); } - private sendMessage = async (name: ProcedureName, input: any): Promise => { - const { result } = await this.http.post<{ result: any }>(`${API_ENDPOINT}/${name}`, { + private sendMessage = async (name: ProcedureName, input: any): Promise => { + const { result } = await this.http.post<{ result: O }>(`${API_ENDPOINT}/${name}`, { body: JSON.stringify(input), }); return result; diff --git a/src/plugins/content_management/server/core/crud.ts b/src/plugins/content_management/server/core/crud.ts index cde3af9f2c2a2..1ac35769d0444 100644 --- a/src/plugins/content_management/server/core/crud.ts +++ b/src/plugins/content_management/server/core/crud.ts @@ -10,7 +10,7 @@ import type { ContentStorage, StorageContext } from './types'; export interface GetResponse { contentTypeId: string; - item?: T; + item: T; } export interface BulkGetResponse { @@ -33,6 +33,11 @@ export interface DeleteItemResponse { result: T; } +export interface SearchResponse { + contentTypeId: string; + result: T; +} + export class ContentCrud implements ContentStorage { private storage: ContentStorage; private eventBus: EventBus; @@ -245,7 +250,7 @@ export class ContentCrud implements ContentStorage { ctx: StorageContext, query: Query, options?: Options - ): Promise> { + ): Promise> { this.eventBus.emit({ type: 'searchItemStart', contentTypeId: this.contentTypeId, diff --git a/src/plugins/content_management/server/index.ts b/src/plugins/content_management/server/index.ts index e56a3b845fa93..659b59ed6880c 100644 --- a/src/plugins/content_management/server/index.ts +++ b/src/plugins/content_management/server/index.ts @@ -14,3 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ContentManagementServerSetup, ContentManagementServerStart } from './types'; +export type { ContentStorage, StorageContext } from './core'; diff --git a/src/plugins/content_management/server/rpc/procedures/bulk_get.ts b/src/plugins/content_management/server/rpc/procedures/bulk_get.ts index ee8c27271abfd..83de5d7fcedf4 100644 --- a/src/plugins/content_management/server/rpc/procedures/bulk_get.ts +++ b/src/plugins/content_management/server/rpc/procedures/bulk_get.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { BulkGetIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/create.ts b/src/plugins/content_management/server/rpc/procedures/create.ts index fe2f4e0ab7d16..9ff7a55fe560c 100644 --- a/src/plugins/content_management/server/rpc/procedures/create.ts +++ b/src/plugins/content_management/server/rpc/procedures/create.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { CreateIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/delete.ts b/src/plugins/content_management/server/rpc/procedures/delete.ts index c10cc02356beb..6260b54468039 100644 --- a/src/plugins/content_management/server/rpc/procedures/delete.ts +++ b/src/plugins/content_management/server/rpc/procedures/delete.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { DeleteIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/get.ts b/src/plugins/content_management/server/rpc/procedures/get.ts index 73960ad8f3a2f..536263b0e9d15 100644 --- a/src/plugins/content_management/server/rpc/procedures/get.ts +++ b/src/plugins/content_management/server/rpc/procedures/get.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { GetIn } from '../../../common'; import type { ContentCrud, StorageContext } from '../../core'; import { validate } from '../../utils'; diff --git a/src/plugins/content_management/server/rpc/procedures/search.ts b/src/plugins/content_management/server/rpc/procedures/search.ts index 4c9d9a4a9b4c9..c0fa0b2c807dc 100644 --- a/src/plugins/content_management/server/rpc/procedures/search.ts +++ b/src/plugins/content_management/server/rpc/procedures/search.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { SearchIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/update.ts b/src/plugins/content_management/server/rpc/procedures/update.ts index 4a9a7951079e5..9ea3b0487d58b 100644 --- a/src/plugins/content_management/server/rpc/procedures/update.ts +++ b/src/plugins/content_management/server/rpc/procedures/update.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { UpdateIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/types.ts b/src/plugins/content_management/server/types.ts index 0f4cc3138b5e9..3d11f487fbe7d 100644 --- a/src/plugins/content_management/server/types.ts +++ b/src/plugins/content_management/server/types.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ +import { CoreApi } from './core'; + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SetupDependencies {} // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ContentManagementServerSetup {} +export interface ContentManagementServerSetup extends CoreApi {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ContentManagementServerStart {} diff --git a/src/plugins/content_management/tsconfig.json b/src/plugins/content_management/tsconfig.json index dfdb4b0f93f2a..a5316fbfac1c1 100644 --- a/src/plugins/content_management/tsconfig.json +++ b/src/plugins/content_management/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types", }, - "include": ["common/**/*", "public/**/*", "server/**/*", "demo/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", "@kbn/config-schema", 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/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/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 1497dd9aa1a37..196ebfa55810f 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -61,13 +61,6 @@ export const handleRequest = ({ searchSource.setField('index', indexPattern); searchSource.setField('size', 0); - // Create a new search source that inherits the original search source - // but has the appropriate timeRange applied via a filter. - // This is a temporary solution until we properly pass down all required - // information for the request to the request handler (https://github.com/elastic/kibana/issues/16641). - // Using callParentStartHandlers: true we make sure, that the parent searchSource - // onSearchRequestStart will be called properly even though we use an inherited - // search source. const timeFilterSearchSource = searchSource.createChild({ callParentStartHandlers: true }); const requestSearchSource = timeFilterSearchSource.createChild({ callParentStartHandlers: true, diff --git a/src/plugins/data/server/search/saved_objects/search_session.ts b/src/plugins/data/server/search/saved_objects/search_session.ts index 5e3906cfd8f63..ef0f960c84e8c 100644 --- a/src/plugins/data/server/search/saved_objects/search_session.ts +++ b/src/plugins/data/server/search/saved_objects/search_session.ts @@ -64,4 +64,14 @@ export const searchSessionSavedObjectType: SavedObjectsType = { }, }, migrations: searchSessionSavedObjectMigrations, + excludeOnUpgrade: async () => { + return { + bool: { + must: [ + { term: { type: SEARCH_SESSION_TYPE } }, + { match: { 'search-session.persisted': false } }, + ], + }, + }; + }, }; 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 65d23292486f3..13fbb9cec243e 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -583,7 +583,7 @@ export class DataViewsService { } catch (err) { if (err instanceof DataViewMissingIndices) { this.onNotification( - { title: err.message, color: 'danger', iconType: 'alert' }, + { title: err.message, color: 'danger', iconType: 'error' }, `refreshFields:${dataView.getIndexPattern()}` ); } @@ -637,7 +637,7 @@ export class DataViewsService { if (err instanceof DataViewMissingIndices) { if (displayErrors) { this.onNotification( - { title: err.message, color: 'danger', iconType: 'alert' }, + { title: err.message, color: 'danger', iconType: 'error' }, `refreshFieldSpecMap:${title}` ); } @@ -807,7 +807,7 @@ export class DataViewsService { { title: err.message, color: 'danger', - iconType: 'alert', + iconType: 'error', }, `initFromSavedObject:${spec.title}` ); 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/files/server/usage/integration_tests/usage.test.ts b/src/plugins/files/server/usage/integration_tests/usage.test.ts index 0bccb869c822e..02cc7dfee55fa 100644 --- a/src/plugins/files/server/usage/integration_tests/usage.test.ts +++ b/src/plugins/files/server/usage/integration_tests/usage.test.ts @@ -44,9 +44,11 @@ describe('Files usage telemetry', () => { request.post(root, `/api/files/shares/${fileKind}/${file3.id}`).send({}).expect(200), ]); - const { body } = await request.get(root, `/api/stats?extended=true&legacy=true`); + const { body } = await request + .post(root, '/api/telemetry/v2/clusters/_stats') + .send({ unencrypted: true }); - expect(body.usage.files).toMatchInlineSnapshot(` + expect(body[0].stats.stack_stats.kibana.plugins.files).toMatchInlineSnapshot(` Object { "countByExtension": Array [ Object { 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/kibana_react/public/notifications/create_notifications.test.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx index 0b2c0a0478d53..0876472d89f1b 100644 --- a/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx +++ b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx @@ -162,7 +162,7 @@ test('can display success, warning and danger toasts', () => { expect(notifications.toasts.add.mock.calls[2][0]).toMatchInlineSnapshot(` Object { "color": "danger", - "iconType": "alert", + "iconType": "error", "onClose": undefined, "text": MountPoint { "reactNode": , diff --git a/src/plugins/kibana_react/public/notifications/create_notifications.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.tsx index 8eb16a5580ab3..ab2cccf7655a3 100644 --- a/src/plugins/kibana_react/public/notifications/create_notifications.tsx +++ b/src/plugins/kibana_react/public/notifications/create_notifications.tsx @@ -40,7 +40,7 @@ export const createNotifications = (services: KibanaServices): KibanaReactNotifi show({ color: 'warning', iconType: 'help', ...input }); const danger: KibanaReactNotifications['toasts']['danger'] = (input) => - show({ color: 'danger', iconType: 'alert', ...input }); + show({ color: 'danger', iconType: 'error', ...input }); const notifications: KibanaReactNotifications = { toasts: { diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index 4c39d02dd55f2..14fc712002abd 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -27,6 +27,9 @@ import { EuiSpacer, EuiTablePagination, IconType, + EuiFormRow, + EuiFieldSearchProps, + EuiFormRowProps, } from '@elastic/eui'; import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; @@ -80,6 +83,8 @@ interface BaseSavedObjectFinder { noItemsMessage?: React.ReactNode; savedObjectMetaData: Array>; showFilter?: boolean; + euiFormRowProps?: Partial; + euiFieldSearchProps?: EuiFieldSearchProps; } interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { @@ -110,6 +115,13 @@ class SavedObjectFinderUi extends React.Component< initialPageSize: PropTypes.oneOf([5, 10, 15, 25]), fixedPageSize: PropTypes.number, showFilter: PropTypes.bool, + euiFormRowProps: PropTypes.object, + euiFieldSearchProps: PropTypes.object, + }; + + static defaultProps = { + euiFormRowProps: {}, + euiFieldSearchProps: {}, }; private isComponentMounted: boolean = false; @@ -336,109 +348,114 @@ class SavedObjectFinderUi extends React.Component< const availableSavedObjectMetaData = this.getAvailableSavedObjectMetaData(); return ( - - - { - this.setState( - { - query: e.target.value, - }, - this.fetchItems - ); - }} - data-test-subj="savedObjectFinderSearchInput" - isLoading={this.state.isFetchingItems} - /> - - - - this.setState({ sortOpen: false })} - button={ - - this.setState(({ sortOpen }) => ({ - sortOpen: !sortOpen, - })) - } - iconType="arrowDown" - isSelected={this.state.sortOpen} - data-test-subj="savedObjectFinderSortButton" - > - {i18n.translate('savedObjects.finder.sortButtonLabel', { - defaultMessage: 'Sort', - })} - - } - > - - - {this.props.showFilter && ( + + + + { + this.setState( + { + query: e.target.value, + }, + this.fetchItems + ); + }} + data-test-subj="savedObjectFinderSearchInput" + isLoading={this.state.isFetchingItems} + {...this.props.euiFieldSearchProps} + /> + + + this.setState({ filterOpen: false })} + isOpen={this.state.sortOpen} + closePopover={() => this.setState({ sortOpen: false })} button={ - this.setState(({ filterOpen }) => ({ - filterOpen: !filterOpen, + this.setState(({ sortOpen }) => ({ + sortOpen: !sortOpen, })) } iconType="arrowDown" - data-test-subj="savedObjectFinderFilterButton" - isSelected={this.state.filterOpen} - numFilters={this.props.savedObjectMetaData.length} - hasActiveFilters={this.state.filteredTypes.length > 0} - numActiveFilters={this.state.filteredTypes.length} + isSelected={this.state.sortOpen} + data-test-subj="savedObjectFinderSortButton" > - {i18n.translate('savedObjects.finder.filterButtonLabel', { - defaultMessage: 'Types', + {i18n.translate('savedObjects.finder.sortButtonLabel', { + defaultMessage: 'Sort', })} } > - ( - { - this.setState(({ filteredTypes }) => ({ - filteredTypes: filteredTypes.includes(metaData.type) - ? filteredTypes.filter((t) => t !== metaData.type) - : [...filteredTypes, metaData.type], - page: 0, - })); - }} - > - {metaData.name} - - ))} - /> + - )} - - - {this.props.children ? {this.props.children} : null} - + {this.props.showFilter && ( + this.setState({ filterOpen: false })} + button={ + + this.setState(({ filterOpen }) => ({ + filterOpen: !filterOpen, + })) + } + iconType="arrowDown" + data-test-subj="savedObjectFinderFilterButton" + isSelected={this.state.filterOpen} + numFilters={this.props.savedObjectMetaData.length} + hasActiveFilters={this.state.filteredTypes.length > 0} + numActiveFilters={this.state.filteredTypes.length} + > + {i18n.translate('savedObjects.finder.filterButtonLabel', { + defaultMessage: 'Types', + })} + + } + > + ( + { + this.setState(({ filteredTypes }) => ({ + filteredTypes: filteredTypes.includes(metaData.type) + ? filteredTypes.filter((t) => t !== metaData.type) + : [...filteredTypes, metaData.type], + page: 0, + })); + }} + > + {metaData.name} + + ))} + /> + + )} + + + {this.props.children ? ( + {this.props.children} + ) : null} + + ); } 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/usage_collection/server/routes/stats/README.md b/src/plugins/usage_collection/server/routes/stats/README.md index 09dabefbab44a..48ebda9cecb05 100644 --- a/src/plugins/usage_collection/server/routes/stats/README.md +++ b/src/plugins/usage_collection/server/routes/stats/README.md @@ -8,9 +8,9 @@ However, the information detailed above can be extended, with the combination of | Query Parameter | Default value | Description | |:----------------|:-------------:|:------------| -|`extended`|`false`|When `true`, it adds `clusterUuid` and `usage`. The latter contains the information reported by all the Usage Collectors registered in the Kibana server. It may throw `503 Stats not ready` if any of the collectors is not fully initialized yet.| -|`legacy`|`false`|By default, when `extended=true`, the key names of the data in `usage` are transformed into API-friendlier `snake_case` format (i.e.: `clusterUuid` is transformed to `cluster_uuid`). When this parameter is `true`, the data is returned as-is.| -|`exclude_usage`|`false`|When `true`, and `extended=true`, it will report `clusterUuid` but no `usage`.| +|`extended`|`false`|When `true`, it adds `clusterUuid`.| +|`legacy`|`false`|By default, when `extended=true`, the key names are transformed into API-friendlier `snake_case` format (i.e.: `clusterUuid` is transformed to `cluster_uuid`). When this parameter is `true`, the data is returned as-is.| +|`exclude_usage`|`true`| Deprecated. Only kept for backward-compatibility. Setting this to `false` has no effect. Usage is always excluded. | ## Known use cases diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts index 9db7f5a8638af..242e93a7554e3 100644 --- a/src/plugins/usage_collection/server/routes/stats/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats/stats.ts @@ -8,14 +8,12 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import defaultsDeep from 'lodash/defaultsDeep'; import { firstValueFrom, Observable } from 'rxjs'; import { ElasticsearchClient, IRouter, type MetricsServiceSetup, - SavedObjectsClientContract, ServiceStatus, ServiceStatusLevels, } from '@kbn/core/server'; @@ -51,14 +49,6 @@ export function registerStatsRoute({ metrics: MetricsServiceSetup; overallStatus$: Observable; }) { - const getUsage = async ( - esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract - ): Promise => { - const usage = await collectorSet.bulkFetchUsage(esClient, savedObjectsClient); - return collectorSet.toObject(usage); - }; - const getClusterUuid = async (asCurrentUser: ElasticsearchClient): Promise => { const body = await asCurrentUser.info({ filter_path: 'cluster_uuid' }); const { cluster_uuid: uuid } = body; @@ -77,7 +67,7 @@ export function registerStatsRoute({ extended: schema.oneOf([schema.literal(''), schema.boolean()], { defaultValue: false }), legacy: schema.oneOf([schema.literal(''), schema.boolean()], { defaultValue: false }), exclude_usage: schema.oneOf([schema.literal(''), schema.boolean()], { - defaultValue: false, + defaultValue: true, }), }), }, @@ -85,61 +75,26 @@ export function registerStatsRoute({ async (context, req, res) => { const isExtended = req.query.extended === '' || req.query.extended; const isLegacy = req.query.legacy === '' || req.query.legacy; - const shouldGetUsage = req.query.exclude_usage === false; let extended; if (isExtended) { const core = await context.core; const { asCurrentUser } = core.elasticsearch.client; - const savedObjectsClient = core.savedObjects.client; - - const [usage, clusterUuid] = await Promise.all([ - shouldGetUsage - ? getUsage(asCurrentUser, savedObjectsClient) - : Promise.resolve({}), - getClusterUuid(asCurrentUser), - ]); - - let modifiedUsage = usage; - if (isLegacy) { - // In an effort to make telemetry more easily augmented, we need to ensure - // we can passthrough the data without every part of the process needing - // to know about the change; however, to support legacy use cases where this - // wasn't true, we need to be backwards compatible with how the legacy data - // looked and support those use cases here. - modifiedUsage = Object.keys(usage).reduce((accum, usageKey) => { - if (usageKey === 'kibana') { - accum = { - ...accum, - ...usage[usageKey], - }; - } else if (usageKey === 'reporting') { - accum = { - ...accum, - xpack: { - ...accum.xpack, - reporting: usage[usageKey], - }, - }; - } else { - // I don't think we need to it this for the above conditions, but do it for most as it will - // match the behavior done in monitoring/bulk_uploader - defaultsDeep(accum, { [usageKey]: usage[usageKey] }); - } - return accum; - }, {} as UsageObject); + const usage = {} as UsageObject; + const clusterUuid = await getClusterUuid(asCurrentUser); - extended = { - usage: modifiedUsage, - clusterUuid, - }; - } else { - extended = collectorSet.toApiFieldNames({ - usage: modifiedUsage, - clusterUuid, - }); - } + // In an effort to make telemetry more easily augmented, we need to ensure + // we can passthrough the data without every part of the process needing + // to know about the change; however, to support legacy use cases where this + // wasn't true, we need to be backwards compatible with how the legacy data + // looked and support those use cases here. + extended = isLegacy + ? { usage, clusterUuid } + : collectorSet.toApiFieldNames({ + usage, + clusterUuid, + }); } // Guaranteed to resolve immediately due to replay effect on getOpsMetrics$ 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/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/api_integration/apis/stats/stats.js b/test/api_integration/apis/stats/stats.js index a95204b5fff4a..3d69a949a4db3 100644 --- a/test/api_integration/apis/stats/stats.js +++ b/test/api_integration/apis/stats/stats.js @@ -91,6 +91,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.cluster_uuid).to.be.a('string'); expect(body.usage).to.be.an('object'); // no usage collectors have been registered so usage is an empty object + expect(body.usage).to.eql({}); assertStatsAndMetrics(body); }); }); @@ -103,6 +104,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.cluster_uuid).to.be.a('string'); expect(body.usage).to.be.an('object'); + expect(body.usage).to.eql({}); assertStatsAndMetrics(body); }); }); @@ -116,6 +118,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.clusterUuid).to.be.a('string'); expect(body.usage).to.be.an('object'); // no usage collectors have been registered so usage is an empty object + expect(body.usage).to.eql({}); assertStatsAndMetrics(body, true); }); }); diff --git a/test/examples/config.js b/test/examples/config.js index fc57b610941d2..2dce755e413b7 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }) { require.resolve('./field_formats'), require.resolve('./partial_results'), require.resolve('./search'), + require.resolve('./content_management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/content_management/index.ts b/test/examples/content_management/index.ts new file mode 100644 index 0000000000000..d5e3f14bfc0e6 --- /dev/null +++ b/test/examples/content_management/index.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from '../../plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ loadTestFile }: PluginFunctionalProviderContext) { + describe('content management examples', function () { + loadTestFile(require.resolve('./todo_app')); + }); +} diff --git a/test/examples/content_management/todo_app.ts b/test/examples/content_management/todo_app.ts new file mode 100644 index 0000000000000..5c8228540a2de --- /dev/null +++ b/test/examples/content_management/todo_app.ts @@ -0,0 +1,72 @@ +/* + * Copyright 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 { Key } from 'selenium-webdriver'; + +import { PluginFunctionalProviderContext } from '../../plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common']); + + describe('Todo app', () => { + it('Todo app works', async () => { + const appId = 'contentManagementExamples'; + await PageObjects.common.navigateToApp(appId); + + // check that initial state is correct + let todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + + // check that filters work + await (await find.byCssSelector('label[title="Completed"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + + await (await find.byCssSelector('label[title="Todo"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + + await (await find.byCssSelector('label[title="All"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + + // check that adding new todo works + await testSubjects.setValue('newTodo', 'New todo'); + await (await testSubjects.find('newTodo')).pressKeys(Key.ENTER); + await retry.tryForTime(1000, async () => { + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(3); + }); + + // check that updating todo works + let newTodo = todos[2]; + expect(await newTodo.getVisibleText()).to.be('New todo'); + let newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox'); + expect(await newTodoCheckbox.isSelected()).to.be(false); + await (await newTodo.findByTagName('label')).click(); + + await (await find.byCssSelector('label[title="Completed"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + newTodo = todos[1]; + expect(await newTodo.getVisibleText()).to.be('New todo'); + newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox'); + expect(await newTodoCheckbox.isSelected()).to.be(true); + + // check that deleting todo works + await (await newTodo.findByCssSelector('[aria-label="Delete"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + }); + }); +} diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/_xjson.ts index 1535337a2a848..445bfa5d42f0a 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/_xjson.ts @@ -15,7 +15,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const log = getService('log'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe('XJSON', function testXjson() { + // FLAKY: https://github.com/elastic/kibana/issues/145477 + describe.skip('XJSON', function testXjson() { this.tags('includeFirefox'); before(async () => { await PageObjects.common.navigateToApp('console'); 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/node_roles_functional/test_suites/all/initializer_context.ts b/test/node_roles_functional/test_suites/all/initializer_context.ts index e616ec8243c9d..43407b7763b66 100644 --- a/test/node_roles_functional/test_suites/all/initializer_context.ts +++ b/test/node_roles_functional/test_suites/all/initializer_context.ts @@ -16,6 +16,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('passes node roles to server PluginInitializerContext', async () => { await supertest.get('/core_plugin_initializer_context/node/roles').expect(200, { backgroundTasks: true, + migrator: false, ui: true, }); }); diff --git a/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts b/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts index e616ec8243c9d..43407b7763b66 100644 --- a/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts +++ b/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts @@ -16,6 +16,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('passes node roles to server PluginInitializerContext', async () => { await supertest.get('/core_plugin_initializer_context/node/roles').expect(200, { backgroundTasks: true, + migrator: false, ui: true, }); }); diff --git a/test/node_roles_functional/test_suites/ui/initializer_context.ts b/test/node_roles_functional/test_suites/ui/initializer_context.ts index 7d4c4f5ada527..28233cfc73c93 100644 --- a/test/node_roles_functional/test_suites/ui/initializer_context.ts +++ b/test/node_roles_functional/test_suites/ui/initializer_context.ts @@ -16,6 +16,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('passes node roles to server PluginInitializerContext', async () => { await supertest.get('/core_plugin_initializer_context/node/roles').expect(200, { backgroundTasks: false, + migrator: false, ui: true, }); }); 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/tsconfig.base.json b/tsconfig.base.json index 2804ac88b2f6f..467fdd7b0f279 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,6 +32,8 @@ "@kbn/alerting-plugin/*": ["x-pack/plugins/alerting/*"], "@kbn/alerts": ["packages/kbn-alerts"], "@kbn/alerts/*": ["packages/kbn-alerts/*"], + "@kbn/alerts-as-data-utils": ["packages/kbn-alerts-as-data-utils"], + "@kbn/alerts-as-data-utils/*": ["packages/kbn-alerts-as-data-utils/*"], "@kbn/alerts-restricted-fixtures-plugin": ["x-pack/test/alerting_api_integration/common/plugins/alerts_restricted"], "@kbn/alerts-restricted-fixtures-plugin/*": ["x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/*"], "@kbn/alerts-ui-shared": ["packages/kbn-alerts-ui-shared"], @@ -158,6 +160,8 @@ "@kbn/console-plugin/*": ["src/plugins/console/*"], "@kbn/content-management-content-editor": ["packages/content-management/content_editor"], "@kbn/content-management-content-editor/*": ["packages/content-management/content_editor/*"], + "@kbn/content-management-examples-plugin": ["examples/content_management_examples"], + "@kbn/content-management-examples-plugin/*": ["examples/content_management_examples/*"], "@kbn/content-management-plugin": ["src/plugins/content_management"], "@kbn/content-management-plugin/*": ["src/plugins/content_management/*"], "@kbn/content-management-table-list": ["packages/content-management/table_list"], @@ -668,6 +672,8 @@ "@kbn/event-log-fixture-plugin/*": ["x-pack/test/plugin_api_integration/plugins/event_log/*"], "@kbn/event-log-plugin": ["x-pack/plugins/event_log"], "@kbn/event-log-plugin/*": ["x-pack/plugins/event_log/*"], + "@kbn/expandable-flyout": ["packages/kbn-expandable-flyout"], + "@kbn/expandable-flyout/*": ["packages/kbn-expandable-flyout/*"], "@kbn/expect": ["packages/kbn-expect"], "@kbn/expect/*": ["packages/kbn-expect/*"], "@kbn/exploratory-view-example-plugin": ["x-pack/examples/exploratory_view_example"], diff --git a/x-pack/performance/configs/cloud_security_posture_config.ts b/x-pack/performance/configs/cloud_security_posture_config.ts new file mode 100644 index 0000000000000..3d4f36bb2c605 --- /dev/null +++ b/x-pack/performance/configs/cloud_security_posture_config.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile( + // eslint-disable-next-line @kbn/imports/no_boundary_crossing + require.resolve('../../test/functional/config.base.js') + ); + + return { + ...xpackFunctionalConfig.getAll(), + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + /** + * Package version is fixed (not latest) so FTR won't suddenly break when package is changed. + * + * test a new package: + * 1. build the package and start the registry with elastic-package and uncomment the 'registryUrl' flag below + * 2. locally checkout the kibana version that matches the new package + * 3. update the package version below to use the new package version + * 4. run tests with NODE_EXTRA_CA_CERTS pointing to the elastic-package certificate. example: + * NODE_EXTRA_CA_CERTS=HOME/.elastic-package/profiles/default/certs/kibana/ca-cert.pem yarn start + * 5. when test pass: + * 1. release a new package to EPR + * 2. merge the updated version number change to kibana + */ + `--xpack.fleet.packages.0.name=cloud_security_posture`, + `--xpack.fleet.packages.0.version=1.2.8`, + // `--xpack.fleet.registryUrl=https://localhost:8080`, + ], + }, + }; +} diff --git a/x-pack/performance/es_archives/kspm_findings/data.json.gz b/x-pack/performance/es_archives/kspm_findings/data.json.gz new file mode 100644 index 0000000000000..075885aacbb45 Binary files /dev/null and b/x-pack/performance/es_archives/kspm_findings/data.json.gz differ diff --git a/x-pack/performance/es_archives/kspm_findings/mappings.json b/x-pack/performance/es_archives/kspm_findings/mappings.json new file mode 100644 index 0000000000000..148507b88cc73 --- /dev/null +++ b/x-pack/performance/es_archives/kspm_findings/mappings.json @@ -0,0 +1,464 @@ +{ + "type": "index", + "value": { + "aliases": {}, + "index": "logs-cloud_security_posture.findings_latest-default", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "cloud_security_posture" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cloudbeat": { + "properties": { + "commit_sha": { + "ignore_above": 1024, + "type": "keyword" + }, + "commit_time": { + "type": "date" + }, + "kubernetes": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "policy": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cluster_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword", + "value": "cloud_security_posture.findings" + }, + "namespace": { + "type": "constant_keyword", + "value": "default" + }, + "type": { + "type": "constant_keyword", + "value": "logs" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "message": { + "type": "match_only_text" + }, + "orchestrator": { + "properties": { + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "ignore_above": 1024, + "type": "wildcard" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "pid": { + "type": "long" + }, + "start": { + "type": "date" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "resource": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw": { + "enabled": false, + "type": "object" + }, + "sub_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "properties": { + "evaluation": { + "ignore_above": 1024, + "type": "keyword" + }, + "evidence": { + "enabled": false, + "type": "object" + }, + "expected": { + "enabled": false, + "type": "object" + } + } + }, + "rule": { + "properties": { + "benchmark": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "posture_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "rule_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "section": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/performance/journeys/cloud_security_dashboard.ts b/x-pack/performance/journeys/cloud_security_dashboard.ts new file mode 100644 index 0000000000000..39625126e7067 --- /dev/null +++ b/x-pack/performance/journeys/cloud_security_dashboard.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 { Journey } from '@kbn/journeys'; +import expect from '@kbn/expect'; + +export const journey = new Journey({ + beforeSteps: async ({ kibanaServer, retry }) => { + await retry.try(async () => { + const response = await kibanaServer.request({ + path: '/internal/cloud_security_posture/status?check=init', + method: 'GET', + }); + expect(response.status).to.eql(200); + expect(response.data).to.eql({ isPluginInitialized: true }); + }); + }, + ftrConfigPath: 'x-pack/performance/configs/cloud_security_posture_config.ts', + esArchives: ['x-pack/performance/es_archives/kspm_findings'], + scalabilitySetup: { + warmup: [ + { + action: 'constantConcurrentUsers', + userCount: 10, + duration: '30s', + }, + { + action: 'rampConcurrentUsers', + minUsersCount: 10, + maxUsersCount: 50, + duration: '2m', + }, + ], + test: [ + { + action: 'constantConcurrentUsers', + userCount: 50, + duration: '3m', + }, + ], + maxDuration: '10m', + }, +}).step('Go to cloud security dashboards Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/security/cloud_security_posture/dashboard`)); + await page.waitForSelector(`[data-test-subj="csp:dashboard-sections-table-header-score"]`); +}); diff --git a/x-pack/performance/tsconfig.json b/x-pack/performance/tsconfig.json index 636c4e1f5ed1f..fb60be2e310ce 100644 --- a/x-pack/performance/tsconfig.json +++ b/x-pack/performance/tsconfig.json @@ -12,5 +12,7 @@ "@kbn/journeys", "@kbn/test-subj-selector", "@kbn/tooling-log", + "@kbn/test", + "@kbn/expect", ] } diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts index b4cd25a4f4126..4fc36193a15d9 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts @@ -6,8 +6,8 @@ */ import { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { type FieldMap } from '@kbn/alerts-as-data-utils'; import { mappingFromFieldMap } from './mapping_from_field_map'; -import { FieldMap } from './types'; export interface GetComponentTemplateFromFieldMapOpts { name: string; diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index 2f2cac2367e8b..f5eeeb8ba6c35 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -4,9 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { alertFieldMap, legacyAlertFieldMap, type FieldMap } from '@kbn/alerts-as-data-utils'; import { mappingFromFieldMap } from './mapping_from_field_map'; -import { FieldMap } from './types'; -import { alertFieldMap } from './alert_field_map'; describe('mappingFromFieldMap', () => { const fieldMap: FieldMap = { @@ -118,6 +117,15 @@ describe('mappingFromFieldMap', () => { date_field: { type: 'date', }, + multifield_field: { + fields: { + text: { + type: 'match_only_text', + }, + }, + ignore_above: 1024, + type: 'keyword', + }, geopoint_field: { type: 'geo_point', }, @@ -131,15 +139,6 @@ describe('mappingFromFieldMap', () => { long_field: { type: 'long', }, - multifield_field: { - fields: { - text: { - type: 'match_only_text', - }, - }, - ignore_above: 1024, - type: 'keyword', - }, nested_array_field: { properties: { field1: { @@ -184,6 +183,9 @@ describe('mappingFromFieldMap', () => { expect(mappingFromFieldMap(alertFieldMap)).toEqual({ dynamic: 'strict', properties: { + '@timestamp': { + type: 'date', + }, kibana: { properties: { alert: { @@ -191,6 +193,9 @@ describe('mappingFromFieldMap', () => { action_group: { type: 'keyword', }, + case_ids: { + type: 'keyword', + }, duration: { properties: { us: { @@ -204,8 +209,18 @@ describe('mappingFromFieldMap', () => { flapping: { type: 'boolean', }, - id: { - type: 'keyword', + flapping_history: { + type: 'boolean', + }, + instance: { + properties: { + id: { + type: 'keyword', + }, + }, + }, + last_detected: { + type: 'date', }, reason: { type: 'keyword', @@ -229,8 +244,8 @@ describe('mappingFromFieldMap', () => { type: 'keyword', }, parameters: { - type: 'object', - enabled: false, + type: 'flattened', + ignore_above: 4096, }, producer: { type: 'keyword', @@ -274,6 +289,58 @@ describe('mappingFromFieldMap', () => { }, }, }); + expect(mappingFromFieldMap(legacyAlertFieldMap)).toEqual({ + dynamic: 'strict', + properties: { + kibana: { + properties: { + alert: { + properties: { + risk_score: { type: 'float' }, + rule: { + properties: { + author: { 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' }, + note: { type: 'keyword' }, + references: { type: 'keyword' }, + rule_id: { type: 'keyword' }, + rule_name_override: { type: 'keyword' }, + to: { type: 'keyword' }, + type: { type: 'keyword' }, + updated_at: { type: 'date' }, + updated_by: { type: 'keyword' }, + version: { type: 'keyword' }, + }, + }, + severity: { type: 'keyword' }, + suppression: { + properties: { + docs_count: { type: 'long' }, + end: { type: 'date' }, + terms: { + properties: { field: { type: 'keyword' }, value: { type: 'keyword' } }, + }, + start: { type: 'date' }, + }, + }, + system_status: { type: 'keyword' }, + workflow_reason: { type: 'keyword' }, + workflow_user: { type: 'keyword' }, + }, + }, + }, + }, + ecs: { properties: { version: { type: 'keyword' } } }, + event: { properties: { action: { type: 'keyword' }, kind: { type: 'keyword' } } }, + tags: { type: 'keyword' }, + }, + }); }); it('uses dynamic setting if specified', () => { diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts index 5a1de7a995b36..9d1db8e577aa5 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts @@ -7,7 +7,7 @@ import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { set } from '@kbn/safer-lodash-set'; -import { FieldMap, MultiField } from './types'; +import type { FieldMap, MultiField } from '@kbn/alerts-as-data-utils'; export function mappingFromFieldMap( fieldMap: FieldMap, @@ -29,7 +29,6 @@ export function mappingFromFieldMap( fields.forEach((field) => { // eslint-disable-next-line @typescript-eslint/naming-convention const { name, required, array, multi_fields, ...rest } = field; - const mapped = multi_fields ? { ...rest, diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts deleted file mode 100644 index b687cbfb0cf7d..0000000000000 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts +++ /dev/null @@ -1,29 +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. - */ - -export interface MultiField { - flat_name?: string; - name: string; - type: string; -} - -export interface FieldMap { - [key: string]: { - type: string; - required: boolean; - array?: boolean; - doc_values?: boolean; - enabled?: boolean; - format?: string; - ignore_above?: number; - index?: boolean; - multi_fields?: MultiField[]; - path?: string; - scaling_factor?: number; - dynamic?: boolean | string; - }; -} diff --git a/x-pack/plugins/alerting/common/alert_schema/index.ts b/x-pack/plugins/alerting/common/alert_schema/index.ts index acca43450fe34..cccb492b10e17 100644 --- a/x-pack/plugins/alerting/common/alert_schema/index.ts +++ b/x-pack/plugins/alerting/common/alert_schema/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { alertFieldMap } from './field_maps/alert_field_map'; +export { mappingFromFieldMap } from './field_maps/mapping_from_field_map'; export { getComponentTemplateFromFieldMap } from './field_maps/component_template_from_field_map'; diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index fafadcdb99ee4..64e5a78d21e8d 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -24,6 +24,8 @@ export * from './parse_duration'; export * from './execution_log_types'; export * from './rule_snooze_type'; +export { mappingFromFieldMap, getComponentTemplateFromFieldMap } from './alert_schema'; + export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; hasPermanentEncryptionKey: boolean; diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 82716f935c6b3..ca64faa7c51ea 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -6,9 +6,11 @@ */ import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { ReplaySubject, Subject } from 'rxjs'; import { AlertsService } from './alerts_service'; +import { IRuleTypeAlerts } from '../types'; let logger: ReturnType; const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -75,40 +77,52 @@ const IlmPutBody = { name: '.alerts-ilm-policy', }; -const getIndexTemplatePutBody = (context?: string) => ({ - name: `.alerts-${context ? context : 'test'}-default-template`, - body: { - index_patterns: [`.alerts-${context ? context : 'test'}-default-*`], - composed_of: [ - 'alerts-common-component-template', - `alerts-${context ? context : 'test'}-component-template`, - ], - template: { - settings: { - auto_expand_replicas: '0-1', - hidden: true, - 'index.lifecycle': { - name: '.alerts-ilm-policy', - rollover_alias: `.alerts-${context ? context : 'test'}-default`, +interface GetIndexTemplatePutBodyOpts { + context?: string; + useLegacyAlerts?: boolean; + useEcs?: boolean; +} +const getIndexTemplatePutBody = (opts?: GetIndexTemplatePutBodyOpts) => { + const context = opts ? opts.context : undefined; + const useLegacyAlerts = opts ? opts.useLegacyAlerts : undefined; + const useEcs = opts ? opts.useEcs : undefined; + return { + name: `.alerts-${context ? context : 'test'}-default-template`, + body: { + index_patterns: [`.alerts-${context ? context : 'test'}-default-*`], + composed_of: [ + `.alerts-${context ? context : 'test'}-mappings`, + ...(useLegacyAlerts ? ['.alerts-legacy-alert-mappings'] : []), + ...(useEcs ? ['.alerts-ecs-mappings'] : []), + '.alerts-framework-mappings', + ], + template: { + settings: { + auto_expand_replicas: '0-1', + hidden: true, + 'index.lifecycle': { + name: '.alerts-ilm-policy', + rollover_alias: `.alerts-${context ? context : 'test'}-default`, + }, + 'index.mapping.total_fields.limit': 2500, + }, + mappings: { + dynamic: false, }, - 'index.mapping.total_fields.limit': 2500, }, - mappings: { - dynamic: false, + _meta: { + managed: true, }, }, - _meta: { - managed: true, - }, - }, -}); + }; +}; -const TestRegistrationContext = { +const TestRegistrationContext: IRuleTypeAlerts = { context: 'test', fieldMap: { field: { type: 'keyword', required: false } }, }; -const AnotherRegistrationContext = { +const AnotherRegistrationContext: IRuleTypeAlerts = { context: 'another', fieldMap: { field: { type: 'keyword', required: false } }, }; @@ -145,10 +159,14 @@ describe('Alerts Service', () => { expect(alertsService.isInitialized()).toEqual(true); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); }); test('should log error and set initialized to false if adding ILM policy throws error', async () => { @@ -185,13 +203,105 @@ describe('Alerts Service', () => { expect(alertsService.isInitialized()).toEqual(false); expect(logger.error).toHaveBeenCalledWith( - `Error installing component template alerts-common-component-template - fail` + `Error installing component template .alerts-framework-mappings - fail` ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); }); + test('should update index template field limit and retry initialization if creating/updating common component template fails with field limit error', async () => { + clusterClient.cluster.putComponentTemplate.mockRejectedValueOnce( + new EsErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + statusCode: 400, + body: { + error: { + root_cause: [ + { + type: 'illegal_argument_exception', + reason: + 'updating component template [.alerts-ecs-mappings] results in invalid composable template [.alerts-security.alerts-default-index-template] after templates are merged', + }, + ], + type: 'illegal_argument_exception', + reason: + 'updating component template [.alerts-ecs-mappings] results in invalid composable template [.alerts-security.alerts-default-index-template] after templates are merged', + caused_by: { + type: 'illegal_argument_exception', + reason: + 'composable template [.alerts-security.alerts-default-index-template] template after composition with component templates [.alerts-ecs-mappings, .alerts-security.alerts-mappings, .alerts-technical-mappings] is invalid', + caused_by: { + type: 'illegal_argument_exception', + reason: + 'invalid composite mappings for [.alerts-security.alerts-default-index-template]', + caused_by: { + type: 'illegal_argument_exception', + reason: 'Limit of total fields [1900] has been exceeded', + }, + }, + }, + }, + }, + }) + ) + ); + const existingIndexTemplate = { + name: 'test-template', + index_template: { + index_patterns: ['test*'], + composed_of: ['.alerts-framework-mappings'], + template: { + settings: { + auto_expand_replicas: '0-1', + hidden: true, + 'index.lifecycle': { + name: '.alerts-ilm-policy', + rollover_alias: `.alerts-empty-default`, + }, + 'index.mapping.total_fields.limit': 1800, + }, + mappings: { + dynamic: false, + }, + }, + }, + }; + clusterClient.indices.getIndexTemplate.mockResolvedValueOnce({ + index_templates: [existingIndexTemplate], + }); + const alertsService = new AlertsService({ + logger, + elasticsearchClientPromise: Promise.resolve(clusterClient), + pluginStop$, + }); + + alertsService.initialize(); + await new Promise((r) => setTimeout(r, 50)); + + expect(alertsService.isInitialized()).toEqual(true); + expect(clusterClient.indices.getIndexTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ + name: existingIndexTemplate.name, + body: { + ...existingIndexTemplate.index_template, + template: { + ...existingIndexTemplate.index_template.template, + settings: { + ...existingIndexTemplate.index_template.template?.settings, + 'index.mapping.total_fields.limit': 2500, + }, + }, + }, + }); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); + // 3x for framework, legacy-alert and ecs mappings, then 1 extra time to update component template + // after updating index template field limit + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + }); + test('should install resources for contexts awaiting initialization when common resources are initialized', async () => { const alertsService = new AlertsService({ logger, @@ -214,20 +324,24 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - // 1x for common component template, 2x for context specific - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); + // 1x for framework component template, 1x for legacy alert, 1x for ecs, 2x for context specific + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(5); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; - expect(componentTemplate2.name).toEqual('alerts-another-component-template'); + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; - expect(componentTemplate3.name).toEqual('alerts-test-component-template'); + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-another-mappings'); + const componentTemplate5 = clusterClient.cluster.putComponentTemplate.mock.calls[4][0]; + expect(componentTemplate5.name).toEqual('.alerts-test-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledTimes(2); expect(clusterClient.indices.putIndexTemplate).toHaveBeenNthCalledWith( 1, - getIndexTemplatePutBody('another') + getIndexTemplatePutBody({ context: 'another' }) ); expect(clusterClient.indices.putIndexTemplate).toHaveBeenNthCalledWith( 2, @@ -291,11 +405,15 @@ describe('Alerts Service', () => { expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; - expect(componentTemplate2.name).toEqual('alerts-test-component-template'); + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( getIndexTemplatePutBody() @@ -318,7 +436,87 @@ describe('Alerts Service', () => { }); }); - test('should not install component template for context fieldMap is empty', async () => { + test('should correctly install resources for context when useLegacyAlerts is true', async () => { + alertsService.register({ ...TestRegistrationContext, useLegacyAlerts: true }); + await new Promise((r) => setTimeout(r, 50)); + expect(await alertsService.isContextInitialized(TestRegistrationContext.context)).toEqual( + true + ); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); + + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); + + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( + getIndexTemplatePutBody({ useLegacyAlerts: true }) + ); + expect(clusterClient.indices.getAlias).toHaveBeenCalledWith({ + index: '.alerts-test-default-*', + }); + expect(clusterClient.indices.putSettings).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.simulateIndexTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.putMapping).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.create).toHaveBeenCalledWith({ + index: '.alerts-test-default-000001', + body: { + aliases: { + '.alerts-test-default': { + is_write_index: true, + }, + }, + }, + }); + }); + + test('should correctly install resources for context when useEcs is true', async () => { + alertsService.register({ ...TestRegistrationContext, useEcs: true }); + await new Promise((r) => setTimeout(r, 50)); + expect(await alertsService.isContextInitialized(TestRegistrationContext.context)).toEqual( + true + ); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); + + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); + + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( + getIndexTemplatePutBody({ useEcs: true }) + ); + expect(clusterClient.indices.getAlias).toHaveBeenCalledWith({ + index: '.alerts-test-default-*', + }); + expect(clusterClient.indices.putSettings).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.simulateIndexTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.putMapping).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.create).toHaveBeenCalledWith({ + index: '.alerts-test-default-000001', + body: { + aliases: { + '.alerts-test-default': { + is_write_index: true, + }, + }, + }, + }); + }); + + test('should not install component template for context if fieldMap is empty', async () => { alertsService.register({ context: 'empty', fieldMap: {}, @@ -328,15 +526,19 @@ describe('Alerts Service', () => { expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ name: `.alerts-empty-default-template`, body: { index_patterns: [`.alerts-empty-default-*`], - composed_of: ['alerts-common-component-template'], + composed_of: ['.alerts-framework-mappings'], template: { settings: { auto_expand_replicas: '0-1', @@ -410,7 +612,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); // putIndexTemplate is skipped but other operations are called as expected expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled(); @@ -443,7 +645,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled(); expect(clusterClient.indices.getAlias).not.toHaveBeenCalled(); @@ -467,7 +669,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).not.toHaveBeenCalled(); @@ -491,7 +693,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putSettings).not.toHaveBeenCalled(); @@ -512,7 +714,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putSettings).not.toHaveBeenCalled(); @@ -535,7 +737,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -559,7 +761,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -581,7 +783,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Failed to PUT mapping for alias alias_1: fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -601,7 +803,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -640,7 +842,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -673,7 +875,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -695,7 +897,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -730,7 +932,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -766,7 +968,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -810,7 +1012,7 @@ describe('Alerts Service', () => { alertsService.initialize(); await new Promise((r) => setTimeout(r, 150)); expect(alertsService.isInitialized()).toEqual(true); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(5); }); test('should retry updating index template for transient ES errors', async () => { diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts index 08643caf862f5..22cb9f4df1884 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts @@ -13,8 +13,14 @@ import { import { get, isEmpty, isEqual } from 'lodash'; import { Logger, ElasticsearchClient } from '@kbn/core/server'; import { firstValueFrom, Observable } from 'rxjs'; -import { FieldMap } from '../../common/alert_schema/field_maps/types'; -import { alertFieldMap } from '../../common/alert_schema'; +import { + alertFieldMap, + ecsFieldMap, + legacyAlertFieldMap, + type FieldMap, +} from '@kbn/alerts-as-data-utils'; +import { IndicesGetIndexTemplateIndexTemplateItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { asyncForEach } from '@kbn/std'; import { DEFAULT_ALERTS_ILM_POLICY_NAME, DEFAULT_ALERTS_ILM_POLICY, @@ -34,7 +40,9 @@ import { const TOTAL_FIELDS_LIMIT = 2500; const INSTALLATION_TIMEOUT = 20 * 60 * 1000; // 20 minutes - +const LEGACY_ALERT_CONTEXT = 'legacy-alert'; +export const ECS_CONTEXT = `ecs`; +export const ECS_COMPONENT_TEMPLATE_NAME = getComponentTemplateName(ECS_CONTEXT); interface AlertsServiceParams { logger: Logger; pluginStop$: Observable; @@ -107,6 +115,16 @@ export class AlertsService implements IAlertsService { const initFns = [ () => this.createOrUpdateIlmPolicy(esClient), () => this.createOrUpdateComponentTemplate(esClient, getComponentTemplate(alertFieldMap)), + () => + this.createOrUpdateComponentTemplate( + esClient, + getComponentTemplate(legacyAlertFieldMap, LEGACY_ALERT_CONTEXT) + ), + () => + this.createOrUpdateComponentTemplate( + esClient, + getComponentTemplate(ecsFieldMap, ECS_CONTEXT) + ), ]; for (const fn of initFns) { @@ -127,7 +145,8 @@ export class AlertsService implements IAlertsService { }); } - public register({ context, fieldMap }: IRuleTypeAlerts, timeoutMs?: number) { + public register(opts: IRuleTypeAlerts, timeoutMs?: number) { + const { context, fieldMap } = opts; // check whether this context has been registered before if (this.registeredContexts.has(context)) { const registeredFieldMap = this.registeredContexts.get(context); @@ -140,37 +159,54 @@ export class AlertsService implements IAlertsService { this.options.logger.info(`Registering resources for context "${context}".`); this.registeredContexts.set(context, fieldMap); - this.resourceInitializationHelper.add({ context, fieldMap }, timeoutMs); + this.resourceInitializationHelper.add(opts, timeoutMs); } - private async initializeContext({ context, fieldMap }: IRuleTypeAlerts, timeoutMs?: number) { + private async initializeContext( + { context, fieldMap, useEcs, useLegacyAlerts }: IRuleTypeAlerts, + timeoutMs?: number + ) { const esClient = await this.options.elasticsearchClientPromise; const indexTemplateAndPattern = getIndexTemplateAndPattern(context); - // Context specific initialization installs component template, index template and write index - // If fieldMap is empty, don't create context specific component template - const initFns = isEmpty(fieldMap) - ? [ - async () => - await this.createOrUpdateIndexTemplate(esClient, indexTemplateAndPattern, [ - getComponentTemplateName(), - ]), - async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), - ] - : [ - async () => - await this.createOrUpdateComponentTemplate( - esClient, - getComponentTemplate(fieldMap, context) - ), - async () => - await this.createOrUpdateIndexTemplate(esClient, indexTemplateAndPattern, [ - getComponentTemplateName(), - getComponentTemplateName(context), - ]), - async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), - ]; + let initFns: Array<() => Promise> = []; + + // List of component templates to reference + const componentTemplateRefs: string[] = []; + + // If fieldMap is not empty, create a context specific component template + if (!isEmpty(fieldMap)) { + const componentTemplate = getComponentTemplate(fieldMap, context); + initFns.push( + async () => await this.createOrUpdateComponentTemplate(esClient, componentTemplate) + ); + componentTemplateRefs.push(componentTemplate.name); + } + + // If useLegacy is set to true, add the legacy alert component template to the references + if (useLegacyAlerts) { + componentTemplateRefs.push(getComponentTemplateName(LEGACY_ALERT_CONTEXT)); + } + + // If useEcs is set to true, add the ECS component template to the references + if (useEcs) { + componentTemplateRefs.push(getComponentTemplateName(ECS_CONTEXT)); + } + + // Add framework component template to the references + componentTemplateRefs.push(getComponentTemplateName()); + + // Context specific initialization installs index template and write index + initFns = initFns.concat([ + async () => + await this.createOrUpdateIndexTemplate( + esClient, + indexTemplateAndPattern, + componentTemplateRefs + ), + async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), + ]); for (const fn of initFns) { await this.installWithTimeout(async () => await fn(), timeoutMs); @@ -200,6 +236,62 @@ export class AlertsService implements IAlertsService { } } + private async getIndexTemplatesUsingComponentTemplate( + esClient: ElasticsearchClient, + componentTemplateName: string + ) { + // Get all index templates and filter down to just the ones referencing this component template + const { index_templates: indexTemplates } = await esClient.indices.getIndexTemplate(); + const indexTemplatesUsingComponentTemplate = (indexTemplates ?? []).filter( + (indexTemplate: IndicesGetIndexTemplateIndexTemplateItem) => + indexTemplate.index_template.composed_of.includes(componentTemplateName) + ); + await asyncForEach( + indexTemplatesUsingComponentTemplate, + async (template: IndicesGetIndexTemplateIndexTemplateItem) => { + await esClient.indices.putIndexTemplate({ + name: template.name, + body: { + ...template.index_template, + template: { + ...template.index_template.template, + settings: { + ...template.index_template.template?.settings, + 'index.mapping.total_fields.limit': TOTAL_FIELDS_LIMIT, + }, + }, + }, + }); + } + ); + } + + private async createOrUpdateComponentTemplateHelper( + esClient: ElasticsearchClient, + template: ClusterPutComponentTemplateRequest + ) { + try { + await esClient.cluster.putComponentTemplate(template); + } catch (error) { + const reason = error?.meta?.body?.error?.caused_by?.caused_by?.caused_by?.reason; + if (reason && reason.match(/Limit of total fields \[\d+\] has been exceeded/) != null) { + // This error message occurs when there is an index template using this component template + // that contains a field limit setting that using this component template exceeds + // Specifically, this can happen for the ECS component template when we add new fields + // to adhere to the ECS spec. Individual index templates specify field limits so if the + // number of new ECS fields pushes the composed mapping above the limit, this error will + // occur. We have to update the field limit inside the index template now otherwise we + // can never update the component template + await this.getIndexTemplatesUsingComponentTemplate(esClient, template.name); + + // Try to update the component template again + await esClient.cluster.putComponentTemplate(template); + } else { + throw error; + } + } + } + private async createOrUpdateComponentTemplate( esClient: ElasticsearchClient, template: ClusterPutComponentTemplateRequest @@ -207,9 +299,12 @@ export class AlertsService implements IAlertsService { this.options.logger.info(`Installing component template ${template.name}`); try { - await retryTransientEsErrors(() => esClient.cluster.putComponentTemplate(template), { - logger: this.options.logger, - }); + await retryTransientEsErrors( + () => this.createOrUpdateComponentTemplateHelper(esClient, template), + { + logger: this.options.logger, + } + ); } catch (err) { this.options.logger.error( `Error installing component template ${template.name} - ${err.message}` diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts b/x-pack/plugins/alerting/server/alerts_service/index.ts similarity index 52% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts rename to x-pack/plugins/alerting/server/alerts_service/index.ts index 0a0060606b423..49247f3baa243 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts +++ b/x-pack/plugins/alerting/server/alerts_service/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -export interface IndexPatternStats { - indexPatternsWithGeoFieldCount: number; - indexPatternsWithGeoPointFieldCount: number; - indexPatternsWithGeoShapeFieldCount: number; - geoShapeAggLayersCount: number; -} +export { + DEFAULT_ALERTS_ILM_POLICY, + DEFAULT_ALERTS_ILM_POLICY_NAME, +} from './default_lifecycle_policy'; +export { ECS_COMPONENT_TEMPLATE_NAME, ECS_CONTEXT } from './alerts_service'; +export { getComponentTemplate } from './types'; diff --git a/x-pack/plugins/alerting/server/alerts_service/types.ts b/x-pack/plugins/alerting/server/alerts_service/types.ts index db47a9a8e0015..aeb73cab6ffd2 100644 --- a/x-pack/plugins/alerting/server/alerts_service/types.ts +++ b/x-pack/plugins/alerting/server/alerts_service/types.ts @@ -6,11 +6,11 @@ */ import { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; -import { getComponentTemplateFromFieldMap } from '../../common/alert_schema'; -import { FieldMap } from '../../common/alert_schema/field_maps/types'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; +import { getComponentTemplateFromFieldMap } from '../../common'; export const getComponentTemplateName = (context?: string) => - `alerts-${context ? context : 'common'}-component-template`; + `.alerts-${context || 'framework'}-mappings`; export interface IIndexPatternString { template: string; @@ -40,5 +40,5 @@ export const getComponentTemplate = ( name: getComponentTemplateName(context), fieldMap, // set field limit slightly higher than actual number of fields - fieldLimit: 100, // Math.round(Object.keys(fieldMap).length * 1.5), + fieldLimit: Math.ceil(Object.keys(fieldMap).length / 1000) * 1000 + 500, }); diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index a13b06596f557..6b5ad3012d8a9 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -56,7 +56,10 @@ export { export { DEFAULT_ALERTS_ILM_POLICY, DEFAULT_ALERTS_ILM_POLICY_NAME, -} from './alerts_service/default_lifecycle_policy'; + ECS_COMPONENT_TEMPLATE_NAME, + ECS_CONTEXT, + getComponentTemplate, +} from './alerts_service'; export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext); diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index e319f315440af..4997d9563d59e 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -22,6 +22,7 @@ import { } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { SharePluginStart } from '@kbn/share-plugin/server'; +import { type FieldMap } from '@kbn/alerts-as-data-utils'; import { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { RulesClient } from './rules_client'; @@ -51,7 +52,6 @@ import { SanitizedRule, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; -import { FieldMap } from '../common/alert_schema/field_maps/types'; import { RulesSettingsFlappingProperties } from '../common/rules_settings'; export type WithoutQueryAndParams = Pick>; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; @@ -172,6 +172,8 @@ export interface IRuleTypeAlerts { context: string; namespace?: string; fieldMap: FieldMap; + useEcs?: boolean; + useLegacyAlerts?: boolean; } export interface RuleType< diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index 1f7017de59a2e..6fed19209ad12 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -39,6 +39,8 @@ "@kbn/data-views-plugin", "@kbn/share-plugin", "@kbn/safer-lodash-set", + "@kbn/alerts-as-data-utils", + "@kbn/core-elasticsearch-client-server-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index d6fc05f286d5a..1fa7ac8774b08 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -72,7 +72,9 @@ node x-pack/plugins/apm/scripts/test/api --runner --basic --updateSnapshots The E2E tests are located in [`x-pack/plugins/apm/ftr_e2e`](../ftr_e2e). -Test runs are recorded to the [Cypress Dashboard](https://dashboard.cypress.io). Tests run on buildkite PR pipeline are parallelized (4 parallel jobs) and are orchestrated by the Cypress dashboard service. It can be configured in [.buildkite/pipelines/pull_request/apm_cypress.yml](https://github.com/elastic/kibana/blob/main/.buildkite/pipelines/pull_request/apm_cypress.yml) with the property `parallelism`. +When PR is labeled with `apm:cypress-record`, test runs are recorded to the [Cypress Dashboard](https://dashboard.cypress.io). + +Tests run on buildkite PR pipeline are parallelized (4 parallel jobs) and are orchestrated by the Cypress dashboard service. It can be configured in [.buildkite/pipelines/pull_request/apm_cypress.yml](https://github.com/elastic/kibana/blob/main/.buildkite/pipelines/pull_request/apm_cypress.yml) with the property `parallelism`. ```yml ... diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts index 578b116a10592..bda4e78f56c48 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts @@ -24,7 +24,7 @@ const serviceRumOverviewHref = url.format({ const testServiveHref = url.format({ pathname: '/app/apm/services/test-service/overview', - query: { rangeFrom: start, rangeTo: end }, + query: { rangeFrom: start, rangeTo: end, transactionType: 'type' }, }); const serviceNodeName = 'opbeans-java-prod-1'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 9c7a52dc5859d..3e2d178357a80 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -5,37 +5,37 @@ * 2.0. */ -import React from 'react'; import { i18n } from '@kbn/i18n'; +import React from 'react'; import { - EuiFlexGroupProps, EuiFlexGroup, + EuiFlexGroupProps, EuiFlexItem, EuiLink, EuiPanel, } from '@elastic/eui'; +import { + isRumAgentName, + isServerlessAgent, +} from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; -import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useTimeRange } from '../../../hooks/use_time_range'; import { useApmRouter } from '../../../hooks/use_apm_router'; +import { useBreakpoints } from '../../../hooks/use_breakpoints'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; +import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart'; import { LatencyChart } from '../../shared/charts/latency_chart'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionColdstartRateChart } from '../../shared/charts/transaction_coldstart_rate_chart'; -import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart'; +import { TransactionsTable } from '../../shared/transactions_table'; import { ServiceOverviewDependenciesTable } from './service_overview_dependencies_table'; import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; -import { TransactionsTable } from '../../shared/transactions_table'; -import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; -import { - isRumAgentName, - isServerlessAgent, -} from '../../../../common/agent_name'; /** * The height a chart should be if it's next to a table with 5 rows and a title. * Add the height of the pagination row. diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx index e269f198b5954..f8b5dc3e0b992 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx @@ -8,6 +8,7 @@ import type { CoreStart } from '@kbn/core/public'; import { Meta, Story } from '@storybook/react'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ServiceOverview } from '.'; import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { MockApmPluginStorybook } from '../../../context/apm_plugin/mock_apm_plugin_storybook'; @@ -19,6 +20,8 @@ const stories: Meta<{}> = { decorators: [ (StoryComponent) => { const serviceName = 'testServiceName'; + const transactionType = 'type'; + const transactionTypeStatus = FETCH_STATUS.SUCCESS; const mockCore = { http: { get: (endpoint: string) => { @@ -37,6 +40,8 @@ const stories: Meta<{}> = { } as unknown as CoreStart; const serviceContextValue = { serviceName, + transactionType, + transactionTypeStatus, } as unknown as APMServiceContextValue; return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 13bd5324f6444..ab6d66ab2130d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -67,7 +67,7 @@ export function ServiceOverviewInstancesChartAndTable({ chartHeight, serviceName, }: ServiceOverviewInstancesChartAndTableProps) { - const { transactionType } = useApmServiceContext(); + const { transactionType, transactionTypeStatus } = useApmServiceContext(); const [tableOptions, setTableOptions] = useState({ pageIndex: 0, sort: DEFAULT_SORT, @@ -95,6 +95,10 @@ export function ServiceOverviewInstancesChartAndTable({ status: mainStatsStatus, } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE_MAIN_STATS); + } + if (!start || !end || !transactionType || !latencyAggregationType) { return; } @@ -125,9 +129,9 @@ export function ServiceOverviewInstancesChartAndTable({ return { // Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched. requestId: uuidv4(), - currentPeriodItems: response.currentPeriod, - currentPeriodItemsCount: response.currentPeriod.length, - previousPeriodItems: response.previousPeriod, + currentPeriodItems: response?.currentPeriod ?? [], + currentPeriodItemsCount: response?.currentPeriod.length, + previousPeriodItems: response?.previousPeriod, }; }); }, @@ -140,6 +144,7 @@ export function ServiceOverviewInstancesChartAndTable({ end, serviceName, transactionType, + transactionTypeStatus, pageIndex, field, direction, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index fcc4f3a97cd5c..30df64a9b4d0c 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -21,7 +21,7 @@ import { asExactTransactionRate } from '../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useEnvironmentsContext } from '../../../context/environments_context/use_environments_context'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; -import { useFetcher } from '../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { usePreferredServiceAnomalyTimeseries } from '../../../hooks/use_preferred_service_anomaly_timeseries'; import { useTimeRange } from '../../../hooks/use_time_range'; import { TimeseriesChartWithContext } from '../../shared/charts/timeseries_chart_with_context'; @@ -60,12 +60,17 @@ export function ServiceOverviewThroughputChart({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const comparisonChartTheme = getComparisonChartTheme(); const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (serviceName && transactionType && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/throughput', @@ -98,6 +103,7 @@ export function ServiceOverviewThroughputChart({ start, end, transactionType, + transactionTypeStatus, offset, transactionName, comparisonEnabled, @@ -111,7 +117,7 @@ export function ServiceOverviewThroughputChart({ const previousPeriodLabel = usePreviousPeriodLabel(); const timeseries = [ { - data: data.currentPeriod, + data: data?.currentPeriod ?? [], type: 'linemark', color: currentPeriodColor, title: i18n.translate('xpack.apm.serviceOverview.throughtputChartTitle', { @@ -121,7 +127,7 @@ export function ServiceOverviewThroughputChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod, + data: data?.previousPeriod ?? [], type: 'area', color: previousPeriodColor, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 21a51babd04b3..21b830fb314af 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { useHistory } from 'react-router-dom'; +import { isServerlessAgent } from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -15,7 +16,6 @@ import { AggregatedTransactionsBadge } from '../../shared/aggregated_transaction import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { replace } from '../../shared/links/url_helpers'; import { TransactionsTable } from '../../shared/transactions_table'; -import { isServerlessAgent } from '../../../../common/agent_name'; export function TransactionOverview() { const { diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index 5b3118e527fa4..f27aab3724ab5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -49,7 +49,10 @@ function setup({ // mock transaction types jest .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') - .mockReturnValue(serviceTransactionTypes); + .mockReturnValue({ + transactionTypes: serviceTransactionTypes, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); // mock agent jest diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx index 57a0e789267b1..a08e575dcd752 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx @@ -49,6 +49,7 @@ export default { { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (transactionType && serviceName && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate', @@ -115,6 +120,7 @@ export function FailedTransactionRateChart({ start, end, transactionType, + transactionTypeStatus, transactionName, offset, comparisonEnabled, @@ -128,7 +134,7 @@ export function FailedTransactionRateChart({ const previousPeriodLabel = usePreviousPeriodLabel(); const timeseries = [ { - data: data.currentPeriod.timeseries, + data: data?.currentPeriod?.timeseries ?? [], type: 'linemark', color: currentPeriodColor, title: i18n.translate('xpack.apm.errorRate.chart.errorRate', { @@ -138,7 +144,7 @@ export function FailedTransactionRateChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod.timeseries, + data: data?.previousPeriod?.timeseries ?? [], type: 'area', color: previousPeriodColor, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts index d203bd8cfe022..6558c6fd03af6 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts @@ -39,6 +39,7 @@ export const onBrushEnd = ({ export function isTimeseriesEmpty(timeseries?: Array>) { return ( !timeseries || + timeseries.length === 0 || timeseries .map((serie) => serie.data) .flat() diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index 23ec2140f5be7..8f084b17381ec 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -84,6 +84,7 @@ const stories: Meta = { value={{ serviceName, transactionType, + transactionTypeStatus: FETCH_STATUS.SUCCESS, transactionTypes: [], fallbackToTransactions: false, serviceAgentStatus: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index 742bef523a143..538e7bb26fef3 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; @@ -31,7 +31,8 @@ export function useTransactionBreakdown({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const { data = { timeseries: undefined }, @@ -39,6 +40,10 @@ export function useTransactionBreakdown({ status, } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve({ timeseries: undefined }); + } + if (serviceName && start && end && transactionType) { return callApmApi( 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', @@ -65,6 +70,7 @@ export function useTransactionBreakdown({ start, end, transactionType, + transactionTypeStatus, transactionName, ] ); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx index 3dfc22a1c2809..b236f92d0f892 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx @@ -18,7 +18,7 @@ import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_te import { isTimeComparison } from '../../time_comparison/get_comparison_options'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { TimeseriesChartWithContext } from '../timeseries_chart_with_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; @@ -71,7 +71,8 @@ export function TransactionColdstartRateChart({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { serviceName, transactionType } = useApmServiceContext(); + const { serviceName, transactionType, transactionTypeStatus } = + useApmServiceContext(); const comparisonChartTheme = getComparisonChartTheme(); const endpoint = transactionName @@ -80,6 +81,10 @@ export function TransactionColdstartRateChart({ const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (transactionType && serviceName && start && end) { return callApmApi(endpoint, { params: { @@ -109,6 +114,7 @@ export function TransactionColdstartRateChart({ start, end, transactionType, + transactionTypeStatus, transactionName, offset, endpoint, @@ -119,7 +125,7 @@ export function TransactionColdstartRateChart({ const timeseries = [ { - data: data.currentPeriod.transactionColdstartRate, + data: data?.currentPeriod?.transactionColdstartRate ?? [], type: 'linemark', color: theme.eui.euiColorVis5, title: i18n.translate('xpack.apm.coldstartRate.chart.coldstartRate', { @@ -129,7 +135,7 @@ export function TransactionColdstartRateChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod.transactionColdstartRate, + data: data?.previousPeriod?.transactionColdstartRate ?? [], type: 'area', color: theme.eui.euiColorMediumShade, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx index f07fc415eaf7d..27eecaf573bf6 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx @@ -44,7 +44,10 @@ function setup({ // mock transaction types jest .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') - .mockReturnValue(serviceTransactionTypes); + .mockReturnValue({ + transactionTypes: serviceTransactionTypes, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); // mock transaction types jest diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 3c2520e9b219e..fc97216c4ae2f 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -132,7 +132,7 @@ export function TransactionsTable({ const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (!start || !end || !latencyAggregationType || !transactionType) { - return; + return Promise.resolve(undefined); } return callApmApi( 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics', @@ -260,10 +260,6 @@ export function TransactionsTable({ transactionOverflowCount, }); - const isLoading = status === FETCH_STATUS.LOADING; - const isNotInitiated = status === FETCH_STATUS.NOT_INITIATED; - const hasFailed = status === FETCH_STATUS.FAILURE; - const pagination = useMemo( () => ({ pageIndex: index, @@ -338,13 +334,14 @@ export function TransactionsTable({ ({ serviceName: '', + transactionTypeStatus: FETCH_STATUS.NOT_INITIATED, transactionTypes: [], fallbackToTransactions: false, serviceAgentStatus: FETCH_STATUS.NOT_INITIATED, @@ -65,11 +67,12 @@ export function ApmServiceContextProvider({ end, }); - const transactionTypes = useServiceTransactionTypesFetcher({ - serviceName, - start, - end, - }); + const { transactionTypes, status: transactionTypeStatus } = + useServiceTransactionTypesFetcher({ + serviceName, + start, + end, + }); const currentTransactionType = getOrRedirectToTransactionType({ transactionType: query.transactionType, @@ -89,6 +92,7 @@ export function ApmServiceContextProvider({ agentName, serverlessType, transactionType: currentTransactionType, + transactionTypeStatus, transactionTypes, runtimeName, fallbackToTransactions, diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index 96a4141e824cc..fa00f7f45e743 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -18,7 +18,7 @@ export function useServiceTransactionTypesFetcher({ start?: string; end?: string; }) { - const { data = INITIAL_DATA } = useFetcher( + const { data = INITIAL_DATA, status } = useFetcher( (callApmApi) => { if (serviceName && start && end) { return callApmApi( @@ -35,5 +35,5 @@ export function useServiceTransactionTypesFetcher({ [serviceName, start, end] ); - return data.transactionTypes; + return { transactionTypes: data.transactionTypes, status }; } diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index 32b0c06e99198..d3640afe9f9ea 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -6,14 +6,14 @@ */ import { useMemo } from 'react'; -import { usePreviousPeriodLabel } from './use_previous_period_text'; import { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options'; -import { useFetcher } from './use_fetcher'; -import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { getLatencyChartSelector } from '../selectors/latency_chart_selectors'; -import { useTimeRange } from './use_time_range'; import { useAnyOfApmParams } from './use_apm_params'; +import { FETCH_STATUS, useFetcher } from './use_fetcher'; +import { usePreviousPeriodLabel } from './use_previous_period_text'; +import { useTimeRange } from './use_time_range'; export function useTransactionLatencyChartsFetcher({ kuery, @@ -22,7 +22,8 @@ export function useTransactionLatencyChartsFetcher({ kuery: string; environment: string; }) { - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const { urlParams: { transactionName, latencyAggregationType }, } = useLegacyUrlParams(); @@ -38,6 +39,10 @@ export function useTransactionLatencyChartsFetcher({ const { data, error, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(undefined); + } + if ( serviceName && start && @@ -76,6 +81,7 @@ export function useTransactionLatencyChartsFetcher({ end, transactionName, transactionType, + transactionTypeStatus, latencyAggregationType, offset, comparisonEnabled, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 86907fdd570be..5b98c8e437782 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -15,10 +15,10 @@ import { PluginInitializerContext, } from '@kbn/core/server'; import { isEmpty, mapValues } from 'lodash'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { APMConfig, APM_SERVER_FEATURE_ID } from '.'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { registerApmRuleTypes } from './routes/alerts/register_apm_rule_types'; @@ -130,25 +130,32 @@ export class APMPlugin ...experimentalRuleFieldMap, [SERVICE_NAME]: { type: 'keyword', + required: false, }, [SERVICE_ENVIRONMENT]: { type: 'keyword', + required: false, }, [TRANSACTION_TYPE]: { type: 'keyword', + required: false, }, [PROCESSOR_EVENT]: { type: 'keyword', + required: false, }, [AGENT_NAME]: { type: 'keyword', + required: false, }, [SERVICE_LANGUAGE_NAME]: { type: 'keyword', + required: false, }, labels: { type: 'object', dynamic: true, + required: false, }, }, 'strict' diff --git a/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts b/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts index 08c8a29ef6712..3ab48db456845 100644 --- a/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts +++ b/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts @@ -44,7 +44,6 @@ export async function scheduleSourceMapMigration({ description: `Migrates fleet source map artifacts to "${APM_SOURCE_MAP_INDEX}" index`, timeout: '1h', maxAttempts: 5, - maxConcurrency: 1, createTaskRunner() { const taskState: TaskState = { isAborted: false }; 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/public/components/case_view/components/case_view_activity.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx index c454a864d9af5..cb2b501ed4041 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx @@ -111,7 +111,10 @@ const usePostPushToServiceMock = usePostPushToService as jest.Mock; const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock; const useGetCaseUsersMock = useGetCaseUsers as jest.Mock; -describe('Case View Page activity tab', () => { +// FLAKY: https://github.com/elastic/kibana/issues/151979 +// FLAKY: https://github.com/elastic/kibana/issues/151980 +// FLAKY: https://github.com/elastic/kibana/issues/151981 +describe.skip('Case View Page activity tab', () => { const caseConnectors = getCaseConnectorsMockResponse(); beforeAll(() => { diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index e44f61315641c..0f1ee58680129 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -94,6 +94,20 @@ export const ConfigureCases: React.FC = React.memo(() => { [refetchActionTypes, refetchCaseConfigure, refetchConnectors, setEditedConnectorItem] ); + const onConnectorCreated = useCallback( + async (createdConnector) => { + const caseConnector = normalizeActionConnector(createdConnector); + + await persistCaseConfigure({ + connector: caseConnector, + closureType, + }); + onConnectorUpdated(createdConnector); + setConnector(caseConnector); + }, + [onConnectorUpdated, closureType, setConnector, persistCaseConfigure] + ); + const isLoadingAny = isLoadingConnectors || persistLoading || loadingCaseConfigure || isLoadingActionTypes; const updateConnectorDisabled = isLoadingAny || !connectorIsValid || connector.id === 'none'; @@ -168,7 +182,7 @@ export const ConfigureCases: React.FC = React.memo(() => { ? triggersActionsUi.getAddConnectorFlyout({ onClose: onCloseAddFlyout, featureId: CasesConnectorFeatureId, - onConnectorCreated: onConnectorUpdated, + onConnectorCreated, }) : null, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx b/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx index b4fb76b8dc94b..40fcf91ffe1fa 100644 --- a/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx +++ b/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx @@ -33,7 +33,8 @@ jest.mock('../../common/lib/kibana'); const useUpdateCommentMock = useUpdateComment as jest.Mock; const patchComment = jest.fn(); -describe(`DescriptionWrapper`, () => { +// FLAKY: +describe.skip(`DescriptionWrapper`, () => { const sampleData = { content: 'what a great comment update', }; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 71750bbc2a515..ae88e17637627 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -28,11 +28,11 @@ import styled from 'styled-components'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { EmbeddablePackageState } from '@kbn/embeddable-plugin/public'; +import { SavedObjectFinderUi } from '@kbn/saved-objects-plugin/public'; import { useKibana } from '../../../../common/lib/kibana'; import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from '../../context'; import { ModalContainer } from './modal_container'; -import { SavedObjectFinderUi } from './saved_objects_finder'; import { useLensDraftComment } from './use_lens_draft_comment'; import { VISUALIZATION } from './translations'; import { useIsMainApplication } from '../../../../common/hooks'; @@ -72,7 +72,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ embeddable, lens, storage, - savedObjects, + http, uiSettings, data: { query: { @@ -362,9 +362,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ savedObjectMetaData={savedObjectMetaData} fixedPageSize={10} uiSettings={uiSettings} - savedObjects={savedObjects} + http={http} euiFieldSearchProps={euiFieldSearchProps} - // @ts-expect-error update types euiFormRowProps={euiFormRowProps} /> diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx deleted file mode 100644 index 5b96ea377fc74..0000000000000 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx +++ /dev/null @@ -1,555 +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. - */ - -// TODO: merge with src/plugins/saved_objects/public/finder/saved_object_finder.tsx - -import { debounce } from 'lodash'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import type { EuiFieldSearchProps, IconType, EuiFormRowProps } from '@elastic/eui'; -import { - EuiContextMenuItem, - EuiContextMenuPanel, - EuiEmptyPrompt, - EuiFieldSearch, - EuiFilterButton, - EuiFilterGroup, - EuiFlexGroup, - EuiFlexItem, - EuiListGroup, - EuiListGroupItem, - EuiLoadingSpinner, - EuiPagination, - EuiPopover, - EuiSpacer, - EuiTablePagination, - EuiFormRow, -} from '@elastic/eui'; -import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; -import { i18n } from '@kbn/i18n'; - -import type { SimpleSavedObject, CoreStart } from '@kbn/core/public'; - -import { LISTING_LIMIT_SETTING } from '@kbn/saved-objects-plugin/public'; - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; - getSavedObjectSubType?(savedObject: SimpleSavedObject): string; - includeFields?: string[]; -} - -interface FinderAttributes { - title?: string; - name?: string; - type: string; -} - -interface SavedObjectFinderState { - items: Array<{ - title: string | null; - name: string | null; - id: SimpleSavedObject['id']; - type: SimpleSavedObject['type']; - savedObject: SimpleSavedObject; - }>; - query: string; - isFetchingItems: boolean; - page: number; - perPage: number; - sortDirection?: Direction; - sortOpen: boolean; - filterOpen: boolean; - filteredTypes: string[]; -} - -interface BaseSavedObjectFinder { - onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], - name: string, - savedObject: SimpleSavedObject - ) => void; - noItemsMessage?: React.ReactNode; - savedObjectMetaData: Array>; - showFilter?: boolean; - euiFormRowProps?: EuiFormRowProps; - euiFieldSearchProps?: EuiFieldSearchProps; -} - -interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { - initialPageSize?: undefined; - fixedPageSize: number; -} - -interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { - initialPageSize?: 5 | 10 | 15 | 25; - fixedPageSize?: undefined; -} - -export type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; - -export type SavedObjectFinderUiProps = { - savedObjects: CoreStart['savedObjects']; - uiSettings: CoreStart['uiSettings']; -} & SavedObjectFinderProps; - -// TODO: Fix this manually. Issue #123375 -// eslint-disable-next-line react/display-name -export class SavedObjectFinderUi extends React.Component< - SavedObjectFinderUiProps, - SavedObjectFinderState -> { - public static propTypes = { - onChoose: PropTypes.func, - noItemsMessage: PropTypes.node, - savedObjectMetaData: PropTypes.array.isRequired, - initialPageSize: PropTypes.oneOf([5, 10, 15, 25]), - fixedPageSize: PropTypes.number, - showFilter: PropTypes.bool, - euiFormRowProps: PropTypes.object, - euiFieldSearchProps: PropTypes.object, - }; - - private isComponentMounted: boolean = false; - - private debouncedFetch = debounce(async (query: string) => { - const metaDataMap = this.getSavedObjectMetaDataMap(); - - const fields = Object.values(metaDataMap) - .map((metaData) => metaData.includeFields || []) - .reduce((allFields, currentFields) => allFields.concat(currentFields), ['title', 'name']); - - const perPage = this.props.uiSettings.get(LISTING_LIMIT_SETTING); - const resp = await this.props.savedObjects.client.find({ - type: Object.keys(metaDataMap), - fields: [...new Set(fields)], - search: query ? `${query}*` : undefined, - page: 1, - perPage, - searchFields: ['title^3', 'description'], - defaultSearchOperator: 'AND', - }); - - resp.savedObjects = resp.savedObjects.filter((savedObject) => { - const metaData = metaDataMap[savedObject.type]; - if (metaData.showSavedObject) { - return metaData.showSavedObject(savedObject); - } else { - return true; - } - }); - - if (!this.isComponentMounted) { - return; - } - - // We need this check to handle the case where search results come back in a different - // order than they were sent out. Only load results for the most recent search. - if (query === this.state.query) { - this.setState({ - isFetchingItems: false, - page: 0, - items: resp.savedObjects.map((savedObject) => { - const { - attributes: { name, title }, - id, - type, - } = savedObject; - const titleToUse = typeof title === 'string' ? title : ''; - const nameToUse = name && typeof name === 'string' ? name : titleToUse; - return { - title: titleToUse, - name: nameToUse, - id, - type, - savedObject, - }; - }), - }); - } - }, 300); - - constructor(props: SavedObjectFinderUiProps) { - super(props); - - this.state = { - items: [], - isFetchingItems: false, - page: 0, - perPage: props.initialPageSize || props.fixedPageSize || 10, - query: '', - filterOpen: false, - filteredTypes: [], - sortOpen: false, - }; - } - - public componentWillUnmount() { - this.isComponentMounted = false; - this.debouncedFetch.cancel(); - } - - public componentDidMount() { - this.isComponentMounted = true; - this.fetchItems(); - } - - public render() { - return ( - <> - {this.renderSearchBar()} - {this.renderListing()} - - ); - } - - private getSavedObjectMetaDataMap(): Record { - return this.props.savedObjectMetaData.reduce( - (map, metaData) => ({ ...map, [metaData.type]: metaData }), - {} - ); - } - - private getPageCount() { - return Math.ceil( - (this.state.filteredTypes.length === 0 - ? this.state.items.length - : this.state.items.filter( - (item) => - this.state.filteredTypes.length === 0 || this.state.filteredTypes.includes(item.type) - ).length) / this.state.perPage - ); - } - - // server-side paging not supported - // 1) saved object client does not support sorting by title because title is only mapped as analyzed - // 2) can not search on anything other than title because all other fields are stored in opaque JSON strings, - // for example, visualizations need to be search by isLab but this is not possible in Elasticsearch side - // with the current mappings - private getPageOfItems = () => { - // do not sort original list to preserve elasticsearch ranking order - const items = this.state.items.slice(); - const { sortDirection } = this.state; - - if (sortDirection || !this.state.query) { - items.sort(({ title: titleA }, { title: titleB }) => { - let order = 1; - if (sortDirection === 'desc') { - order = -1; - } - return order * (titleA || '').toLowerCase().localeCompare((titleB || '').toLowerCase()); - }); - } - - // If begin is greater than the length of the sequence, an empty array is returned. - const startIndex = this.state.page * this.state.perPage; - // If end is greater than the length of the sequence, slice extracts through to the end of the sequence (arr.length). - const lastIndex = startIndex + this.state.perPage; - return items - .filter( - (item) => - this.state.filteredTypes.length === 0 || this.state.filteredTypes.includes(item.type) - ) - .slice(startIndex, lastIndex); - }; - - private fetchItems = () => { - this.setState( - { - isFetchingItems: true, - }, - this.debouncedFetch.bind(null, this.state.query) - ); - }; - - private getAvailableSavedObjectMetaData() { - const typesInItems = new Set(); - this.state.items.forEach((item) => { - typesInItems.add(item.type); - }); - return this.props.savedObjectMetaData.filter((metaData) => typesInItems.has(metaData.type)); - } - - private getSortOptions() { - const sortOptions = [ - { - this.setState({ - sortDirection: 'asc', - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc', { - defaultMessage: 'Ascending', - })} - , - { - this.setState({ - sortDirection: 'desc', - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc', { - defaultMessage: 'Descending', - })} - , - ]; - if (this.state.query) { - sortOptions.push( - { - this.setState({ - sortDirection: undefined, - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto', { - defaultMessage: 'Best match', - })} - - ); - } - return sortOptions; - } - - private renderSearchBar() { - const availableSavedObjectMetaData = this.getAvailableSavedObjectMetaData(); - - return ( - - - - { - this.setState( - { - query: e.target.value, - }, - this.fetchItems - ); - }} - data-test-subj="savedObjectFinderSearchInput" - isLoading={this.state.isFetchingItems} - {...(this.props.euiFieldSearchProps || {})} - /> - - - - this.setState({ sortOpen: false })} - button={ - - this.setState(({ sortOpen }) => ({ - sortOpen: !sortOpen, - })) - } - iconType="arrowDown" - isSelected={this.state.sortOpen} - data-test-subj="savedObjectFinderSortButton" - > - {i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel', - { - defaultMessage: 'Sort', - } - )} - - } - > - - - {this.props.showFilter && ( - this.setState({ filterOpen: false })} - button={ - - this.setState(({ filterOpen }) => ({ - filterOpen: !filterOpen, - })) - } - iconType="arrowDown" - data-test-subj="savedObjectFinderFilterButton" - isSelected={this.state.filterOpen} - numFilters={this.props.savedObjectMetaData.length} - hasActiveFilters={this.state.filteredTypes.length > 0} - numActiveFilters={this.state.filteredTypes.length} - > - {i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel', - { - defaultMessage: 'Types', - } - )} - - } - > - ( - { - this.setState(({ filteredTypes }) => ({ - filteredTypes: filteredTypes.includes(metaData.type) - ? filteredTypes.filter((t) => t !== metaData.type) - : [...filteredTypes, metaData.type], - page: 0, - })); - }} - > - {metaData.name} - - ))} - /> - - )} - - - {this.props.children ? ( - {this.props.children} - ) : null} - - - ); - } - - private renderListing() { - const items = this.state.items.length === 0 ? [] : this.getPageOfItems(); - const { onChoose, savedObjectMetaData } = this.props; - - return ( - <> - {this.state.isFetchingItems && this.state.items.length === 0 && ( - - - - - - - )} - {items.length > 0 ? ( - <> - - - {items.map((item) => { - const currentSavedObjectMetaData = savedObjectMetaData.find( - (metaData) => metaData.type === item.type - ); - - if (currentSavedObjectMetaData == null) { - return null; - } - - const fullName = currentSavedObjectMetaData.getTooltipForSavedObject - ? currentSavedObjectMetaData.getTooltipForSavedObject(item.savedObject) - : `${item.name} (${currentSavedObjectMetaData.name})`; - - const iconType = ( - currentSavedObjectMetaData || - ({ - getIconForSavedObject: () => 'document', - } as Pick, 'getIconForSavedObject'>) - ).getIconForSavedObject(item.savedObject); - - return ( - { - onChoose(item.id, item.type, fullName, item.savedObject); - } - : undefined - } - title={fullName} - data-test-subj={`savedObjectTitle${(item.title || '').split(' ').join('-')}`} - /> - ); - })} - - - ) : ( - !this.state.isFetchingItems && - )} - {this.getPageCount() > 1 && - (this.props.fixedPageSize ? ( - { - this.setState({ - page, - }); - }} - /> - ) : ( - { - this.setState({ - page, - }); - }} - onChangeItemsPerPage={(perPage) => { - this.setState({ - perPage, - }); - }} - itemsPerPage={this.state.perPage} - itemsPerPageOptions={[5, 10, 15, 25]} - /> - ))} - - ); - } -} diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts index e32aa462d7121..1d70808f14db2 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts @@ -26,6 +26,7 @@ export const createCaseSavedObjectType = ( namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { assignees: { properties: { diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index c795b7d960e61..1d02a26f732c8 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -20,6 +20,7 @@ export const createCaseCommentSavedObjectType = ({ namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { comment: { type: 'text', @@ -32,115 +33,34 @@ export const createCaseCommentSavedObjectType = ({ }, actions: { properties: { - targets: { - type: 'nested', - properties: { - hostname: { type: 'keyword' }, - endpointId: { type: 'keyword' }, - }, - }, type: { type: 'keyword' }, }, }, alertId: { type: 'keyword', }, - index: { - type: 'keyword', - }, created_at: { type: 'date', }, created_by: { properties: { - full_name: { - type: 'keyword', - }, username: { type: 'keyword', }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - externalReferenceId: { - type: 'keyword', - }, - externalReferenceStorage: { - dynamic: false, - properties: { - // externalReferenceStorage.type - type: { - type: 'keyword', - }, }, }, externalReferenceAttachmentTypeId: { type: 'keyword', }, - externalReferenceMetadata: { - dynamic: false, - properties: {}, - }, persistableStateAttachmentTypeId: { type: 'keyword', }, - persistableStateAttachmentState: { - dynamic: false, - properties: {}, - }, pushed_at: { type: 'date', }, - pushed_by: { - properties: { - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - rule: { - properties: { - id: { - type: 'keyword', - }, - name: { - type: 'keyword', - }, - }, - }, updated_at: { type: 'date', }, - updated_by: { - properties: { - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, }, }, migrations: () => createCommentsMigrations(migrationDeps), diff --git a/x-pack/plugins/cases/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts index 2ee1e3458c647..879640c4b6e40 100644 --- a/x-pack/plugins/cases/server/saved_object_types/configure.ts +++ b/x-pack/plugins/cases/server/saved_object_types/configure.ts @@ -15,71 +15,17 @@ export const caseConfigureSavedObjectType: SavedObjectsType = { namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { created_at: { type: 'date', }, - created_by: { - properties: { - email: { - type: 'keyword', - }, - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - connector: { - properties: { - name: { - type: 'text', - }, - type: { - type: 'keyword', - }, - fields: { - properties: { - key: { - type: 'text', - }, - value: { - type: 'text', - }, - }, - }, - }, - }, closure_type: { type: 'keyword', }, owner: { type: 'keyword', }, - updated_at: { - type: 'date', - }, - updated_by: { - properties: { - email: { - type: 'keyword', - }, - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, }, }, migrations: configureMigrations, diff --git a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts index 7d89b090847d3..1335570e8a132 100644 --- a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts +++ b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts @@ -15,20 +15,8 @@ export const caseConnectorMappingsSavedObjectType: SavedObjectsType = { namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { - mappings: { - properties: { - source: { - type: 'keyword', - }, - target: { - type: 'keyword', - }, - action_type: { - type: 'keyword', - }, - }, - }, owner: { type: 'keyword', }, diff --git a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts index 067ecaaf8fe1e..d8c442b39dab1 100644 --- a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts @@ -18,6 +18,7 @@ export const createCaseUserActionSavedObjectType = ( namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { action: { type: 'keyword', @@ -27,18 +28,9 @@ export const createCaseUserActionSavedObjectType = ( }, created_by: { properties: { - email: { - type: 'keyword', - }, username: { type: 'keyword', }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, }, }, payload: { 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/upgrade_assistant/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cloud_defend/public/pages/index.ts similarity index 74% rename from x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/index.ts rename to x-pack/plugins/cloud_defend/public/pages/index.ts index 5e6e379bd9b2b..ec35f87e70811 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cloud_defend/public/pages/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { telemetrySavedObjectMigrations } from './telemetry_saved_object_migrations'; +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/mocks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts index ceefc2a2b6333..219b551c14260 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts @@ -15,11 +15,15 @@ import { } from '../../../common/constants'; import type { PostureInput } from '../../../common/types'; -export const getMockPolicyAWS = () => getPolicyMock(CLOUDBEAT_AWS); -export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA); -export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS); +export const getMockPolicyAWS = () => getPolicyMock(CLOUDBEAT_AWS, 'cspm', 'aws'); +export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA, 'kspm', 'self_managed'); +export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS, 'kspm', 'eks'); -const getPolicyMock = (type: PostureInput): NewPackagePolicy => { +const getPolicyMock = ( + type: PostureInput, + posture: string, + deployment: string +): NewPackagePolicy => { const mockPackagePolicy = createNewPackagePolicyMock(); const awsVarsMock = { @@ -44,10 +48,10 @@ const getPolicyMock = (type: PostureInput): NewPackagePolicy => { }, vars: { posture: { - value: type === CLOUDBEAT_VANILLA || type === CLOUDBEAT_EKS ? 'kspm' : 'cspm', + value: posture, type: 'text', }, - deployment: { value: type, type: 'text' }, + deployment: { value: deployment, type: 'text' }, }, inputs: [ { diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts index aaed63a91da0a..8e7366d9b8664 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts @@ -35,7 +35,7 @@ describe('getPosturePolicy', () => { const policy = getPosturePolicy(mockCisAws, 'cloudbeat/cis_k8s'); expect(policy.vars?.posture.value).toBe('kspm'); - expect(policy.vars?.deployment.value).toBe('cloudbeat/cis_k8s'); + expect(policy.vars?.deployment.value).toBe('self_managed'); // Does not change extra vars expect(policy.vars?.extra.value).toBe('value'); 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 a8a179673d568..86e0a4ec1d054 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 @@ -43,8 +43,36 @@ export const isPostureInput = ( SUPPORTED_POLICY_TEMPLATES.includes(input.policy_template as CloudSecurityPolicyTemplate) && SUPPORTED_CLOUDBEAT_INPUTS.includes(input.type as PostureInput); -const getInputPolicyTemplate = (inputs: NewPackagePolicyInput[], inputType: PostureInput) => - inputs.filter(isPostureInput).find((i) => i.type === inputType)!.policy_template; +const getPostureType = (policyTemplateInput: PostureInput) => { + switch (policyTemplateInput) { + case CLOUDBEAT_AWS: + case CLOUDBEAT_AZURE: + case CLOUDBEAT_GCP: + return 'cspm'; + case CLOUDBEAT_VANILLA: + case CLOUDBEAT_EKS: + return 'kspm'; + default: + return 'n/a'; + } +}; + +const getDeploymentType = (policyTemplateInput: PostureInput) => { + switch (policyTemplateInput) { + case CLOUDBEAT_AWS: + return 'aws'; + case CLOUDBEAT_AZURE: + return 'azure'; + case CLOUDBEAT_GCP: + return 'gcp'; + case CLOUDBEAT_VANILLA: + return 'self_managed'; + case CLOUDBEAT_EKS: + return 'eks'; + default: + return 'n/a'; + } +}; const getPostureInput = ( input: NewPackagePolicyInput, @@ -83,8 +111,8 @@ export const getPosturePolicy = ( inputs: newPolicy.inputs.map((item) => getPostureInput(item, inputType, inputVars)), // Set hidden policy vars vars: merge({}, newPolicy.vars, { - deployment: { value: inputType }, - posture: { value: getInputPolicyTemplate(newPolicy.inputs, inputType) }, + deployment: { value: getDeploymentType(inputType) }, + posture: { value: getPostureType(inputType) }, }), }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx index f8ac3dc3ad53a..b25149208ce1a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import numeral from '@elastic/numeral'; -import { Link, generatePath } from 'react-router-dom'; +import { generatePath, Link } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { ColumnNameWithTooltip } from '../../../components/column_name_with_tooltip'; import { ComplianceScoreBar } from '../../../components/compliance_score_bar'; @@ -126,7 +126,9 @@ const baseColumns: Array> = width: '15%', render: (resourceId: FindingsByResourcePage['resource_id']) => ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 268ea6f393a99..a9598b228f3ae 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -69,6 +69,7 @@ const getResourceFindingSharedValues = (sharedValues: { resourceSubType: string; resourceName: string; clusterId: string; + cloudAccountName: string; }): EuiDescriptionListProps['listItems'] => [ { title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.resourceTypeTitle', { @@ -88,10 +89,18 @@ const getResourceFindingSharedValues = (sharedValues: { }), description: sharedValues.clusterId, }, + { + title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.cloudAccountName', { + defaultMessage: 'Cloud Account Name', + }), + description: sharedValues.cloudAccountName, + }, ]; export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); + const decodedResourceId = decodeURIComponent(params.resourceId); + const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY); @@ -111,7 +120,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const resourceFindings = useResourceFindings({ sort: urlQuery.sort, query: baseEsQuery.query, - resourceId: params.resourceId, + resourceId: decodedResourceId, enabled: !baseEsQuery.error, }); @@ -213,10 +222,11 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { resourceFindings.data && ( ) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts index a2314dc50b50d..17520e68bceb9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts @@ -35,7 +35,7 @@ type ResourceFindingsResponse = IKibanaSearchResponse< >; export type ResourceFindingsResponseAggs = Record< - 'count' | 'clusterId' | 'resourceSubType' | 'resourceName', + 'count' | 'clusterId' | 'resourceSubType' | 'resourceName' | 'cloudAccountName', estypes.AggregationsMultiBucketAggregateBase< estypes.AggregationsStringRareTermsBucketKeys | undefined > @@ -59,6 +59,9 @@ const getResourceFindingsQuery = ({ sort: [{ [sort.field]: sort.direction }], aggs: { ...getFindingsCountAggQuery(), + cloudAccountName: { + terms: { field: 'cloud.account.name' }, + }, clusterId: { terms: { field: 'cluster_id' }, }, @@ -98,6 +101,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { assertNonBucketsArray(aggregations.clusterId?.buckets); assertNonBucketsArray(aggregations.resourceSubType?.buckets); assertNonBucketsArray(aggregations.resourceName?.buckets); + assertNonBucketsArray(aggregations.cloudAccountName?.buckets); return { page: hits.hits.map((hit) => hit._source!), @@ -106,6 +110,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { clusterId: getFirstBucketKey(aggregations.clusterId?.buckets), resourceSubType: getFirstBucketKey(aggregations.resourceSubType?.buckets), resourceName: getFirstBucketKey(aggregations.resourceName?.buckets), + cloudAccountName: getFirstBucketKey(aggregations.cloudAccountName?.buckets), }; }, onError: (err: Error) => showErrorToast(toasts, err), 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/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index 134fb7ebf395e..a2c37a2ba7264 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -19,4 +19,5 @@ export enum ErrorCode { RESOURCE_NOT_FOUND = 'resource_not_found', UNAUTHORIZED = 'unauthorized', UNCAUGHT_EXCEPTION = 'uncaught_exception', + ENGINE_NOT_FOUND = 'engine_not_found', } 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/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/components/engine/engine_search_preview/engine_search_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx index f4491baf6a7a6..f31fb2dd911a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx @@ -9,7 +9,7 @@ import React, { useState, useMemo } from 'react'; import { useValues } from 'kea'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; import { PagingInfo, Results, @@ -25,7 +25,9 @@ import EnginesAPIConnector, { } from '@elastic/search-ui-engines-connector'; import { HttpSetup } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../shared/doc_links'; import { HttpLogic } from '../../../../shared/http'; import { EngineViewTabs } from '../../../routes'; import { EnterpriseSearchEnginesPageTemplate } from '../../layout/engines_page_template'; @@ -138,6 +140,13 @@ export const EngineSearchPreview: React.FC = () => { + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx index 97b1a1608ab60..c685cf5898e3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx @@ -194,26 +194,33 @@ export const ResultsPerPageView: React.FC = ({ options, value, }) => ( - - - - - ({ - text: i18n.translate( - 'xpack.enterpriseSearch.content.engine.searchPreview.resultsPerPage.label', - { - defaultMessage: '{value} {value, plural, one {Result} other {Results}}', - values: { value: option }, - } - ), - value: option, - })) ?? [] - } - value={value} - onChange={(evt) => onChange(parseInt(evt.target.value, 10))} - /> - + + + + + + ({ + text: i18n.translate( + 'xpack.enterpriseSearch.content.engine.searchPreview.resultsPerPage.option.label', + { + defaultMessage: '{value} {value, plural, one {Result} other {Results}}', + values: { value: option }, + } + ), + value: option, + })) ?? [] + } + value={value} + onChange={(evt) => onChange(parseInt(evt.target.value, 10))} + /> + + ); 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/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/flash_messages/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/constants.ts index 35e1942bdc3de..fb490ce35b7fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/constants.ts @@ -11,7 +11,7 @@ export const FLASH_MESSAGE_TYPES = { success: { color: 'success' as FlashMessageColors, iconType: 'check' }, info: { color: 'primary' as FlashMessageColors, iconType: 'iInCircle' }, warning: { color: 'warning' as FlashMessageColors, iconType: 'alert' }, - error: { color: 'danger' as FlashMessageColors, iconType: 'alert' }, + error: { color: 'danger' as FlashMessageColors, iconType: 'error' }, }; // This is the default amount of time (5 seconds) a toast will last before disappearing diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.test.tsx index 757e5509773ac..dd1aef467d3dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.test.tsx @@ -69,7 +69,7 @@ describe('Toasts', () => { }, { color: 'danger', - iconType: 'alert', + iconType: 'error', title: 'Oh no!', text:
      Something went wrong
      , id: 'errorToastId', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts index 6d56c7b202797..25da2c2f728a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts @@ -104,7 +104,7 @@ describe('Flash Message Helpers', () => { expect(FlashMessagesLogic.values.toastMessages).toEqual([ { color: 'danger', - iconType: 'alert', + iconType: 'error', title: 'Something went wrong', id: 'errorToast-1234567890', }, @@ -142,7 +142,7 @@ describe('Flash Message Helpers', () => { expect(FlashMessagesLogic.values.toastMessages).toEqual([ { color: 'danger', - iconType: 'alert', + iconType: 'error', title: 'Something went wrong', text: "Here's some helpful advice on what to do", toastLifeTimeMs: 50000, 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/engines/field_capabilities.test.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts new file mode 100644 index 0000000000000..3e250e4ec9109 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts @@ -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 { FieldCapsResponse } from '@elastic/elasticsearch/lib/api/types'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { EnterpriseSearchEngineDetails } from '../../../common/types/engines'; + +import { fetchEngineFieldCapabilities } from './field_capabilities'; + +describe('engines field_capabilities', () => { + const mockClient = { + asCurrentUser: { + fieldCaps: jest.fn(), + }, + asInternalUser: {}, + }; + const mockEngine: EnterpriseSearchEngineDetails = { + created: '1999-12-31T23:59:59.999Z', + indices: [], + name: 'unit-test-engine', + updated: '1999-12-31T23:59:59.999Z', + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('gets engine alias field capabilities', async () => { + const fieldCapsResponse = {} as FieldCapsResponse; + + mockClient.asCurrentUser.fieldCaps.mockResolvedValueOnce(fieldCapsResponse); + await expect( + fetchEngineFieldCapabilities(mockClient as unknown as IScopedClusterClient, mockEngine) + ).resolves.toEqual({ + created: mockEngine.created, + field_capabilities: fieldCapsResponse, + name: mockEngine.name, + updated: mockEngine.updated, + }); + + expect(mockClient.asCurrentUser.fieldCaps).toHaveBeenCalledTimes(1); + expect(mockClient.asCurrentUser.fieldCaps).toHaveBeenCalledWith({ + fields: '*', + include_unmapped: true, + index: 'search-engine-unit-test-engine', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts new file mode 100644 index 0000000000000..ed42ab744621c --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.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-elasticsearch-server'; + +import { + EnterpriseSearchEngineDetails, + EnterpriseSearchEngineFieldCapabilities, +} from '../../../common/types/engines'; + +export const fetchEngineFieldCapabilities = async ( + client: IScopedClusterClient, + engine: EnterpriseSearchEngineDetails +): Promise => { + const { created, name, updated } = engine; + const fieldCapabilities = await client.asCurrentUser.fieldCaps({ + fields: '*', + include_unmapped: true, + index: getEngineIndexAliasName(name), + }); + return { + created, + field_capabilities: fieldCapabilities, + name, + updated, + }; +}; + +// Note: This will likely need to be modified when engines move to es module +const getEngineIndexAliasName = (engineName: string): string => `search-engine-${engineName}`; 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/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts index 83e31f1ddf03d..019cca3a7acc2 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts @@ -7,6 +7,19 @@ import { mockDependencies, mockRequestHandler, MockRouter } from '../../__mocks__'; +jest.mock('../../utils/fetch_enterprise_search', () => ({ + ...jest.requireActual('../../utils/fetch_enterprise_search'), + fetchEnterpriseSearch: jest.fn(), +})); +jest.mock('../../lib/engines/field_capabilities', () => ({ + fetchEngineFieldCapabilities: jest.fn(), +})); + +import { RequestHandlerContext } from '@kbn/core/server'; + +import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities'; +import { fetchEnterpriseSearch } from '../../utils/fetch_enterprise_search'; + import { registerEnginesRoutes } from './engines'; describe('engines routes', () => { @@ -298,4 +311,118 @@ describe('engines routes', () => { mockRouter.shouldThrow(request); }); }); + + describe('GET /internal/enterprise_search/engines/{engine_name}/field_capabilities', () => { + let mockRouter: MockRouter; + const mockClient = { + asCurrentUser: {}, + }; + const mockCore = { + elasticsearch: { client: mockClient }, + savedObjects: { client: {} }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/engines/{engine_name}/field_capabilities', + }); + + registerEnginesRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fetches engine fields', async () => { + const engineResult = { + created: '1999-12-31T23:59:59.999Z', + indices: [], + name: 'unit-test', + updated: '1999-12-31T23:59:59.999Z', + }; + const fieldCapabilitiesResult = { + name: 'unit-test', + }; + + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce(engineResult); + (fetchEngineFieldCapabilities as jest.Mock).mockResolvedValueOnce(fieldCapabilitiesResult); + + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(fetchEnterpriseSearch).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + '/api/engines/unit-test' + ); + expect(fetchEngineFieldCapabilities).toHaveBeenCalledWith(mockClient, engineResult); + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: fieldCapabilitiesResult, + headers: { 'content-type': 'application/json' }, + }); + }); + it('returns 404 when fetch engine is undefined', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce(undefined); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'engine_not_found', + }, + message: 'Could not find engine', + }, + statusCode: 404, + }); + }); + it('returns 404 when fetch engine is returns 404', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce({ + responseStatus: 404, + responseStatusText: 'NOT_FOUND', + }); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'engine_not_found', + }, + message: 'Could not find engine', + }, + statusCode: 404, + }); + }); + it('returns error when fetch engine returns an error', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce({ + responseStatus: 500, + responseStatusText: 'INTERNAL_SERVER_ERROR', + }); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'uncaught_exception', + }, + message: 'Error fetching engine', + }, + statusCode: 500, + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts index 71b0061a7d1ba..111c04e6cd42e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts @@ -6,15 +6,22 @@ */ import { schema } from '@kbn/config-schema'; +import { EnterpriseSearchEngineDetails } from '../../../common/types/engines'; +import { ErrorCode } from '../../../common/types/error_codes'; import { createApiKey } from '../../lib/engines/create_api_key'; +import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities'; import { RouteDependencies } from '../../plugin'; + +import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { fetchEnterpriseSearch, isResponseError } from '../../utils/fetch_enterprise_search'; export function registerEnginesRoutes({ - router, + config, enterpriseSearchRequestHandler, log, + router, }: RouteDependencies) { router.get( { @@ -134,8 +141,37 @@ export function registerEnginesRoutes({ path: '/internal/enterprise_search/engines/{engine_name}/field_capabilities', validate: { params: schema.object({ engine_name: schema.string() }) }, }, - enterpriseSearchRequestHandler.createRequest({ - path: '/api/engines/:engine_name/field_capabilities', + elasticsearchErrorHandler(log, async (context, request, response) => { + const engineName = decodeURIComponent(request.params.engine_name); + const { client } = (await context.core).elasticsearch; + + const engine = await fetchEnterpriseSearch( + config, + request, + `/api/engines/${engineName}` + ); + if (!engine || (isResponseError(engine) && engine.responseStatus === 404)) { + return createError({ + errorCode: ErrorCode.ENGINE_NOT_FOUND, + message: 'Could not find engine', + response, + statusCode: 404, + }); + } + if (isResponseError(engine)) { + return createError({ + errorCode: ErrorCode.UNCAUGHT_EXCEPTION, + message: 'Error fetching engine', + response, + statusCode: engine.responseStatus, + }); + } + + const data = await fetchEngineFieldCapabilities(client, engine); + return response.ok({ + body: data, + headers: { 'content-type': 'application/json' }, + }); }) ); } 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/enterprise_search/server/utils/fetch_enterprise_search.test.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.test.ts new file mode 100644 index 0000000000000..20fe7b57350ee --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import '../__mocks__/http_agent.mock'; + +jest.mock('node-fetch'); +import fetch from 'node-fetch'; + +import { KibanaRequest } from '@kbn/core/server'; + +import { ConfigType } from '..'; + +import { fetchEnterpriseSearch, isResponseError } from './fetch_enterprise_search'; + +describe('fetchEnterpriseSearch', () => { + const mockConfig = { + accessCheckTimeout: 200, + accessCheckTimeoutWarning: 100, + host: 'http://localhost:3002', + }; + const mockRequest = { + headers: { authorization: '==someAuth' }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns json fetch response', async () => { + const response = { foo: 'bar' }; + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce(response), + ok: true, + }); + await expect( + fetchEnterpriseSearch(mockConfig as ConfigType, mockRequest as KibanaRequest, '/api/v1/test') + ).resolves.toBe(response); + }); + it('calls expected endpoint', async () => { + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce({}), + ok: true, + }); + await fetchEnterpriseSearch( + mockConfig as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith('http://localhost:3002/api/v1/test', expect.anything()); + }); + it('uses request auth header & config custom headers', async () => { + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce({}), + ok: true, + }); + const config = { + ...mockConfig, + customHeaders: { + foo: 'bar', + }, + }; + await fetchEnterpriseSearch( + config as unknown as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(expect.anything(), { + agent: expect.anything(), + headers: { + Authorization: mockRequest.headers.authorization, + foo: 'bar', + }, + }); + }); + it('returns undefined when config.host is unavailable', async () => { + await expect( + fetchEnterpriseSearch( + { host: '' } as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ) + ).resolves.toBeUndefined(); + }); +}); + +describe('isResponseError', () => { + it('returns true for ResponseError object', () => { + expect(isResponseError({ responseStatus: 404, responseStatusText: 'NOT_FOUND' })).toBe(true); + }); + it('returns false for null/undefined', () => { + expect(isResponseError(null)).toBe(false); + expect(isResponseError(undefined)).toBe(false); + }); + it('returns false for object without expected keys', () => { + expect(isResponseError({})).toBe(false); + expect(isResponseError({ responseStatusText: 'NOT_FOUND' })).toBe(false); + expect(isResponseError({ responseStatus: 404 })).toBe(false); + expect(isResponseError([])).toBe(false); + }); + it('returns false for non-object', () => { + expect(isResponseError(100)).toBe(false); + expect(isResponseError('test')).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.ts new file mode 100644 index 0000000000000..b1a2146a86ab9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.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 fetch, { RequestInit } from 'node-fetch'; + +import { KibanaRequest } from '@kbn/core/server'; + +import { ConfigType } from '..'; + +import { entSearchHttpAgent } from '../lib/enterprise_search_http_agent'; + +export interface ResponseError { + responseStatus: number; + responseStatusText: string; +} + +export function isResponseError(resp: unknown): resp is ResponseError { + if (typeof resp !== 'object') return false; + if (resp === null) return false; + if ('responseStatus' in resp && 'responseStatusText' in resp) return true; + return false; +} + +export async function fetchEnterpriseSearch( + config: ConfigType, + request: KibanaRequest, + endpoint: string +): Promise { + if (!config.host) return undefined; + + const enterpriseSearchUrl = encodeURI(`${config.host}${endpoint}`); + const options: RequestInit = { + agent: entSearchHttpAgent.getHttpAgent(), + headers: { + Authorization: request.headers.authorization as string, + ...config.customHeaders, + }, + }; + + const response = await fetch(enterpriseSearchUrl, options); + + if (!response.ok) { + return { + responseStatus: response.status, + responseStatusText: response.statusText, + }; + } + + return await response.json(); +} diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index acb1360d39f83..eb7dcf0cad55c 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2078,7 +2078,7 @@ "required": true } ], - "put": { + "post": { "summary": "Agent - Reassign", "tags": [], "responses": { @@ -2120,6 +2120,50 @@ } } } + }, + "put": { + "summary": "Agent - Reassign", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "reassign-agent-deprecated", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "policy_id": { + "type": "string" + } + }, + "required": [ + "policy_id" + ] + } + } + } + }, + "deprecated": true } }, "/agents/{agentId}/unenroll": { diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 7127151e5c2c3..5f67c8630b0dc 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1301,7 +1301,7 @@ paths: name: agentId in: path required: true - put: + post: summary: Agent - Reassign tags: [] responses: @@ -1327,6 +1327,33 @@ paths: type: string required: - policy_id + put: + summary: Agent - Reassign + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + $ref: '#/components/responses/error' + operationId: reassign-agent-deprecated + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + policy_id: + type: string + required: + - policy_id + deprecated: true /agents/{agentId}/unenroll: parameters: - schema: 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 4827cc77fc634..af00d1563854e 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 @@ -4,7 +4,7 @@ parameters: name: agentId in: path required: true -put: +post: summary: Agent - Reassign tags: [] responses: @@ -30,4 +30,31 @@ put: type: string required: - policy_id +put: + summary: Agent - Reassign + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + $ref: ../components/responses/error.yaml + operationId: reassign-agent-deprecated + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + policy_id: + type: string + required: + - policy_id + deprecated: true diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 7a73829838d55..603eeed136059 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -119,15 +119,25 @@ export type PostBulkAgentUpgradeResponse = BulkAgentAction; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PostAgentUpgradeResponse {} +// deprecated export interface PutAgentReassignRequest { params: { agentId: string; }; body: { policy_id: string }; } - +// deprecated // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PutAgentReassignResponse {} +export interface PostAgentReassignRequest { + params: { + agentId: string; + }; + body: { policy_id: string }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostAgentReassignResponse {} export interface PostBulkAgentReassignRequest { body: { diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index bf9db5435be82..4a8076fd09836 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -17,7 +17,6 @@ "navigation", "customIntegrations", "share", - "spaces", "security", "unifiedSearch", "savedObjectsTagging", @@ -33,7 +32,8 @@ "globalSearch", "telemetry", "discover", - "ingestPipelines" + "ingestPipelines", + "spaces", ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx index 79582e94a6226..b9135b3dc716b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { EuiLoadingContent, EuiSteps } from '@elastic/eui'; +import { EuiSkeletonText, EuiSteps } from '@elastic/eui'; import { useAdvancedForm } from './hooks'; import { useLatestFleetServers } from './hooks/use_latest_fleet_servers'; @@ -83,7 +83,7 @@ export const AdvancedTab: React.FunctionComponent = ({ ]; return isSelectFleetServerPolicyLoading ? ( - + ) : ( ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 772d90ada7dc1..5e6443986c7c6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -11,13 +11,13 @@ import { EuiText, EuiSpacer, EuiLink, - EuiLoadingContent, EuiLoadingSpinner, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, formatDate, EuiDescriptionList, + EuiSkeletonText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -188,7 +188,7 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent = ({ )} - + ); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 5f0b64051f26f..4459b8f762382 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -15,8 +15,8 @@ import { EuiFlexItem, EuiPanel, EuiIcon, + EuiSkeletonText, EuiToolTip, - EuiLoadingContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; @@ -52,15 +52,47 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ {[ { - title: i18n.translate('xpack.fleet.agentDetails.cpuLabel', { - defaultMessage: 'CPU', - }), + title: ( + + } + > + + +   + + + + ), description: formatAgentCPU(agent.metrics, agentPolicy), }, { - title: i18n.translate('xpack.fleet.agentDetails.memoryLabel', { - defaultMessage: 'Memory', - }), + title: ( + + } + > + + +   + + + + ), description: formatAgentMemory(agent.metrics, agentPolicy), }, ].map(({ title, description }) => { @@ -128,7 +160,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ description: agentPolicy ? ( ) : ( - + ), }, { @@ -213,7 +245,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ /> ) ) : ( - + ), }, { @@ -233,7 +265,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ /> ) ) : ( - + ), }, { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index 180a1b894ca13..7ec0de9023e32 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -15,9 +15,9 @@ import { EuiFlexItem, EuiIcon, EuiLink, - EuiLoadingContent, EuiLoadingSpinner, EuiText, + EuiSkeletonText, formatDate, } from '@elastic/eui'; import React, { useCallback, useEffect, useState } from 'react'; @@ -272,7 +272,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent {isLoading ? ( - + ) : ( items={diagnosticsEntries} columns={columns} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx index cb3f0d77eed34..5fd6183863fb6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx @@ -19,7 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { Agent } from '../../../../types'; import { - sendPutAgentReassign, + sendPostAgentReassign, sendPostBulkAgentReassign, useStartServices, useGetAgentPolicies, @@ -71,7 +71,7 @@ export const AgentReassignAgentPolicyModal: React.FunctionComponent = ({ throw new Error('No selected agent policy id'); } const res = isSingleAgent - ? await sendPutAgentReassign((agents[0] as Agent).id, { + ? await sendPostAgentReassign((agents[0] as Agent).id, { policy_id: selectedAgentPolicyId, }) : await sendPostBulkAgentReassign({ diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx index 101de2a30cef5..74580fede42bf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx @@ -133,7 +133,7 @@ function usePackageInstall({ />, { theme$ } ), - iconType: 'alert', + iconType: 'error', }); } else { setPackageInstallStatus({ name, status: InstallStatus.installed, version }); @@ -224,7 +224,7 @@ function usePackageInstall({ />, { theme$ } ), - iconType: 'alert', + iconType: 'error', }); } else { setPackageInstallStatus({ name, status: InstallStatus.notInstalled, version: null }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx index 67ea0781bafc2..ffbc34ab2c55e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { EuiLoadingContent } from '@elastic/eui'; +import { EuiSkeletonText } from '@elastic/eui'; import { INTEGRATIONS_ROUTING_PATHS } from '../../constants'; import { IntegrationsStateContextProvider, useBreadcrumbs } from '../../hooks'; @@ -34,7 +34,7 @@ export const EPMApp: React.FunctionComponent = () => { - }> + }> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx index 4ef6dde3fb2f6..a4ab3c19200bc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx @@ -8,13 +8,13 @@ import React, { useEffect, useState } from 'react'; import { EuiCodeBlock, - EuiLoadingContent, EuiModal, EuiModalBody, EuiModalHeader, EuiModalFooter, EuiModalHeaderTitle, EuiButton, + EuiSkeletonText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -58,10 +58,10 @@ export const NoticeModal: React.FunctionComponent = ({ noticePath, onClos // Simulate a long notice while loading <>

      - +

      - +

      )} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx index 582bbb6dfd31e..52b4f8fbe4fdc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiLoadingContent, EuiText } from '@elastic/eui'; +import { EuiText, EuiSkeletonText } from '@elastic/eui'; import React, { Fragment, useEffect, useState } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -57,13 +57,13 @@ export function Readme({ {/* simulates a long page of text loading */}

      - +

      - +

      - +

      )} diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts index ca53345bb5799..4a23950b0bf56 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts @@ -26,8 +26,8 @@ import type { PostBulkAgentUnenrollRequest, PostBulkAgentUnenrollResponse, PostAgentUnenrollResponse, - PutAgentReassignRequest, - PutAgentReassignResponse, + PostAgentReassignRequest, + PostAgentReassignResponse, PostBulkAgentReassignRequest, PostBulkAgentReassignResponse, GetAgentsRequest, @@ -126,13 +126,13 @@ export function sendGetAgentTags(query: GetAgentsRequest['query'], options?: Req }); } -export function sendPutAgentReassign( +export function sendPostAgentReassign( agentId: string, - body: PutAgentReassignRequest['body'], + body: PostAgentReassignRequest['body'], options?: RequestOptions ) { - return sendRequest({ - method: 'put', + return sendRequest({ + method: 'post', path: agentRouteService.getReassignPath(agentId), body, ...options, diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 530b8701ab9f4..8ce5cc30cde9e 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -69,8 +69,8 @@ export type { GetAgentIncomingDataRequest, IncomingDataList, GetAgentIncomingDataResponse, - PutAgentReassignRequest, - PutAgentReassignResponse, + PostAgentReassignRequest, + PostAgentReassignResponse, PostBulkAgentReassignRequest, PostBulkAgentReassignResponse, PostNewAgentActionResponse, diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 4c1d37e3d0f52..8e228f445977e 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -75,12 +75,7 @@ export const createAppContextStartContractMock = ( kibanaBranch: 'main', telemetryEventsSender: createMockTelemetryEventsSender(), bulkActionsResolver: {} as any, - messageSigningService: { - isEncryptionAvailable: true, - generateKeyPair: jest.fn(), - sign: jest.fn(), - getPublicKey: jest.fn(), - }, + messageSigningService: createMessageSigningServiceMock(), }; }; @@ -165,3 +160,12 @@ export const createMockAgentClient = () => agentServiceMock.createClient(); * Creates a mock PackageService */ export const createMockPackageService = () => packageServiceMock.create(); + +export function createMessageSigningServiceMock() { + return { + isEncryptionAvailable: true, + generateKeyPair: jest.fn(), + sign: jest.fn(), + getPublicKey: jest.fn(), + }; +} diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 265431a87e265..4e9a7f19b1818 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -8,7 +8,7 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { take, filter } from 'rxjs/operators'; - +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { i18n } from '@kbn/i18n'; import type { CoreSetup, @@ -118,7 +118,7 @@ export interface FleetSetupDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; - spaces: SpacesPluginStart; + spaces?: SpacesPluginStart; telemetry?: TelemetryPluginSetup; taskManager: TaskManagerSetupContract; } @@ -386,7 +386,7 @@ export class FleetPlugin return getInternalSoClient(); }, get spaceId() { - return deps.spaces.spacesService.getSpaceId(request); + return deps.spaces?.spacesService?.getSpaceId(request) ?? DEFAULT_SPACE_ID; }, get limitedToPackages() { diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 24dd0d355411d..b001b27d15b27 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -29,6 +29,7 @@ import type { GetAvailableVersionsResponse, GetActionStatusResponse, GetAgentUploadsResponse, + PostAgentReassignResponse, } from '../../../common/types'; import type { GetAgentsRequestSchema, @@ -38,7 +39,8 @@ import type { DeleteAgentRequestSchema, GetAgentStatusRequestSchema, GetAgentDataRequestSchema, - PutAgentReassignRequestSchema, + PutAgentReassignRequestSchemaDeprecated, + PostAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, PostBulkUpdateAgentTagsRequestSchema, GetActionStatusRequestSchema, @@ -234,10 +236,10 @@ export const getAgentTagsHandler: RequestHandler< } }; -export const putAgentsReassignHandler: RequestHandler< - TypeOf, +export const putAgentsReassignHandlerDeprecated: RequestHandler< + TypeOf, undefined, - TypeOf + TypeOf > = async (context, request, response) => { const coreContext = await context.core; const soClient = coreContext.savedObjects.client; @@ -257,6 +259,29 @@ export const putAgentsReassignHandler: RequestHandler< } }; +export const postAgentsReassignHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { + await AgentService.reassignAgent( + soClient, + esClient, + request.params.agentId, + request.body.policy_id + ); + + const body: PostAgentReassignResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; + export const postBulkAgentsReassignHandler: RequestHandler< undefined, undefined, diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index d7eb3657cbfd6..9e5204e64ac26 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -21,7 +21,8 @@ import { GetAgentStatusRequestSchema, GetAgentDataRequestSchema, PostNewAgentActionRequestSchema, - PutAgentReassignRequestSchema, + PutAgentReassignRequestSchemaDeprecated, + PostAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema, @@ -46,7 +47,7 @@ import { updateAgentHandler, deleteAgentHandler, getAgentStatusForAgentPolicyHandler, - putAgentsReassignHandler, + putAgentsReassignHandlerDeprecated, postBulkAgentsReassignHandler, getAgentDataHandler, bulkUpdateAgentTagsHandler, @@ -54,6 +55,7 @@ import { getActionStatusHandler, getAgentUploadsHandler, getAgentUploadFileHandler, + postAgentsReassignHandler, } from './handlers'; import { postNewAgentActionHandlerBuilder, @@ -178,15 +180,27 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT postAgentUnenrollHandler ); + // mark as deprecated router.put( { path: AGENT_API_ROUTES.REASSIGN_PATTERN, - validate: PutAgentReassignRequestSchema, + validate: PutAgentReassignRequestSchemaDeprecated, fleetAuthz: { fleet: { all: true }, }, }, - putAgentsReassignHandler + putAgentsReassignHandlerDeprecated + ); + + router.post( + { + path: AGENT_API_ROUTES.REASSIGN_PATTERN, + validate: PostAgentReassignRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + postAgentsReassignHandler ); router.post( 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/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index 44e0014630d15..2f148a2f10805 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -447,6 +447,27 @@ describe('EPM template', () => { expect(mappings).toEqual(keywordWithMultiFieldsMapping); }); + it('tests processing date field with format', () => { + const dateWithFormatYml = ` +- name: dateWithFormat + type: date + date_format: yyyy-MM-dd +`; + + const dateWithMapping = { + properties: { + dateWithFormat: { + type: 'date', + format: 'yyyy-MM-dd', + }, + }, + }; + const fields: Field[] = safeLoad(dateWithFormatYml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(mappings).toEqual(dateWithMapping); + }); + it('tests processing wildcard field with multi fields', () => { const keywordWithMultiFieldsLiteralYml = ` - name: keywordWithMultiFields diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index ac7afd143666b..dacdfae720ccf 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -322,6 +322,10 @@ function _generateMappings( fieldProps.type = 'alias'; fieldProps.path = field.path; break; + case 'date': + const dateMappings = generateDateMapping(field); + fieldProps = { ...fieldProps, ...dateMappings, type: 'date' }; + break; default: fieldProps.type = type; } @@ -423,6 +427,14 @@ function generateWildcardMapping(field: Field): IndexTemplateMapping { return mapping; } +function generateDateMapping(field: Field): IndexTemplateMapping { + const mapping: IndexTemplateMapping = {}; + if (field.date_format) { + mapping.format = field.date_format; + } + return mapping; +} + /** * Generates the template name out of the given information */ 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/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts index b8af566463896..4bec0604cca4f 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/field.ts +++ b/x-pack/plugins/fleet/server/services/epm/fields/field.ts @@ -17,6 +17,7 @@ export interface Field { description?: string; value?: string; format?: string; + date_format?: string; fields?: Fields; enabled?: boolean; path?: string; 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/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 92b0f098ae19d..598ea0fa67fe1 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -120,7 +120,16 @@ export const PostBulkAgentUpgradeRequestSchema = { }), }; -export const PutAgentReassignRequestSchema = { +export const PutAgentReassignRequestSchemaDeprecated = { + params: schema.object({ + agentId: schema.string(), + }), + body: schema.object({ + policy_id: schema.string(), + }), +}; + +export const PostAgentReassignRequestSchema = { params: schema.object({ agentId: schema.string(), }), diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx index ce30172a74f15..d2fd0639859a6 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -34,6 +34,24 @@ export default { }, } as Meta; +const fakeDataView = { + title: 'metricbeat-*', + fields: [ + { + name: 'system.cpu.user.pct', + type: 'number', + }, + { + name: 'system.cpu.system.pct', + type: 'number', + }, + { + name: 'system.cpu.cores', + type: 'number', + }, + ], +}; + const CustomEquationEditorTemplate: Story = (args) => { const [expression, setExpression] = useState(args.expression); const [errors, setErrors] = useState(args.errors); @@ -60,6 +78,7 @@ const CustomEquationEditorTemplate: Story = (args) => errors={errors} expression={expression} onChange={handleExpressionChange} + dataView={fakeDataView} /> ); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx index 866e818688533..8632616866a7e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx @@ -16,6 +16,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import { omit, range, first, xor, debounce } from 'lodash'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { DataViewBase } from '@kbn/es-query'; import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/http_api'; import { Aggregators, @@ -39,6 +40,7 @@ export interface CustomEquationEditorProps { fields: NormalizedFields; aggregationTypes: AggregationTypes; errors: IErrorObject; + dataView: DataViewBase; } const NEW_METRIC = { name: 'A', aggType: Aggregators.AVERAGE as CustomMetricAggTypes }; @@ -53,6 +55,7 @@ export const CustomEquationEditor = ({ fields, aggregationTypes, errors, + dataView, }: CustomEquationEditorProps) => { const [customMetrics, setCustomMetrics] = useState( expression?.customMetrics ?? [NEW_METRIC] @@ -130,6 +133,7 @@ export const CustomEquationEditor = ({ disableDelete={disableDelete} onChange={handleChange} errors={errors} + dataView={dataView} /> ); } diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx index 43ac682830bcd..78bc2bf265b4e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx @@ -4,16 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - EuiFieldText, - EuiFormRow, - EuiHorizontalRule, - EuiFlexItem, - EuiFlexGroup, - EuiSelect, -} from '@elastic/eui'; +import { EuiFormRow, EuiHorizontalRule, EuiFlexItem, EuiFlexGroup, EuiSelect } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { DataViewBase } from '@kbn/es-query'; +import { MetricsExplorerKueryBar } from '../../../../pages/metrics/metrics_explorer/components/kuery_bar'; import { Aggregators, CustomMetricAggTypes } from '../../../../../common/alerting/metrics'; import { MetricRowControls } from './metric_row_controls'; import { MetricRowBaseProps } from './types'; @@ -21,6 +16,7 @@ import { MetricRowBaseProps } from './types'; interface MetricRowWithCountProps extends MetricRowBaseProps { agg?: Aggregators; filter?: string; + dataView: DataViewBase; } export const MetricRowWithCount = ({ @@ -31,6 +27,7 @@ export const MetricRowWithCount = ({ disableDelete, onChange, aggregationTypes, + dataView, }: MetricRowWithCountProps) => { const aggOptions = useMemo( () => @@ -59,10 +56,10 @@ export const MetricRowWithCount = ({ ); const handleFilterChange = useCallback( - (el: React.ChangeEvent) => { + (filterString: string) => { onChange({ name, - filter: el.target.value, + filter: filterString, aggType: agg as CustomMetricAggTypes, }); }, @@ -89,7 +86,14 @@ export const MetricRowWithCount = ({ { defaultMessage: 'KQL Filter {name}', values: { name } } )} > - +
      diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 5bc6cff8545e1..224bf8d1fb5d0 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -311,6 +311,7 @@ export const Expressions: React.FC = (props) => { setRuleParams={updateParams} errors={(errors[idx] as IErrorObject) || emptyError} expression={e || {}} + dataView={derivedIndexPattern} > { timeWindowSize: [], }} expression={expression} + dataView={{ fields: [], title: 'metricbeat-*' }} /> ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index 14ff5b1e60eed..a353b954386c1 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -27,6 +27,7 @@ import { ThresholdExpression, WhenExpression, } from '@kbn/triggers-actions-ui-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; import { Aggregators, Comparator } from '../../../../common/alerting/metrics'; import { decimalToPct, pctToDecimal } from '../../../../common/utils/corrected_percent_convert'; import { DerivedIndexPattern } from '../../../containers/metrics_source'; @@ -54,6 +55,7 @@ interface ExpressionRowProps { addExpression(): void; remove(id: number): void; setRuleParams(id: number, params: MetricExpression): void; + dataView: DataViewBase; } const StyledExpressionRow = euiStyled(EuiFlexGroup)` @@ -74,8 +76,17 @@ const StyledHealth = euiStyled(EuiHealth)` export const ExpressionRow: React.FC = (props) => { const [isExpanded, setRowState] = useState(true); const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); - const { children, setRuleParams, expression, errors, expressionId, remove, fields, canDelete } = - props; + const { + dataView, + children, + setRuleParams, + expression, + errors, + expressionId, + remove, + fields, + canDelete, + } = props; const { aggType = AGGREGATION_TYPES.MAX, @@ -325,6 +336,7 @@ export const ExpressionRow: React.FC = (props) => { aggregationTypes={aggregationType} onChange={handleCustomMetricChange} errors={errors} + dataView={dataView} /> diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx index b3d4d423c58b5..83d3919ef6c01 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { isEmpty } from 'lodash'; @@ -48,7 +49,7 @@ export function validateMetricThreshold({ }; metric: string[]; customMetricsError?: string; - customMetrics: Record; + customMetrics: Record; equation?: string; }; } & { filterQuery?: string[] } = {}; @@ -169,7 +170,7 @@ export function validateMetricThreshold({ ); } else { c.customMetrics.forEach((metric) => { - const customMetricErrors: { aggType?: string; field?: string } = {}; + const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; if (!metric.aggType) { customMetricErrors.aggType = i18n.translate( 'xpack.infra.metrics.alertFlyout.error.customMetrics.aggTypeRequired', @@ -186,6 +187,13 @@ export function validateMetricThreshold({ } ); } + if (metric.aggType === 'count' && metric.filter) { + try { + fromKueryExpression(metric.filter); + } catch (e) { + customMetricErrors.filter = e.message; + } + } if (!isEmpty(customMetricErrors)) { errors[id].customMetrics[metric.name] = customMetricErrors; } diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts index 228d479f39ee2..4b2ee6d8a2eda 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts @@ -53,8 +53,8 @@ export class RX implements ILensVisualization { const dataLayer = this.formula.insertOrReplaceFormulaColumn( 'y_network_in_bytes', { - formula: "counter_rate(max(system.network.in.bytes), kql='system.network.in.bytes: *') * 8", - timeScale: 's', + formula: + "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", format: { id: 'bits', params: { @@ -95,13 +95,13 @@ export class RX implements ILensVisualization { negate: false, alias: null, index: '3be1e71b-4bc5-4462-a314-04539f877a19', - key: 'system.network.in.bytes', + key: 'host.network.ingress.bytes', value: 'exists', type: 'exists', }, query: { exists: { - field: 'system.network.in.bytes', + field: 'host.network.ingress.bytes', }, }, $state: { diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts index da5e4635d55ca..236efed938046 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts @@ -54,8 +54,7 @@ export class TX implements ILensVisualization { 'y_network_out_bytes', { formula: - "counter_rate(max(system.network.out.bytes), kql='system.network.out.bytes: *') * 8", - timeScale: 's', + "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", format: { id: 'bits', params: { @@ -96,13 +95,13 @@ export class TX implements ILensVisualization { negate: false, alias: null, index: '3be1e71b-4bc5-4462-a314-04539f877a19', - key: 'system.network.out.bytes', + key: 'host.network.egress.bytes', value: 'exists', type: 'exists', }, query: { exists: { - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }, }, $state: { diff --git a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index e264da646949c..657db92072275 100644 --- a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -24,6 +24,7 @@ interface AutocompleteFieldProps { disabled?: boolean; autoFocus?: boolean; 'aria-label'?: string; + compressed?: boolean; } interface AutocompleteFieldState { @@ -53,6 +54,7 @@ export class AutocompleteField extends React.Component< value, disabled, 'aria-label': ariaLabel, + compressed, } = this.props; const { areSuggestionsVisible, selectedIndex } = this.state; @@ -60,6 +62,7 @@ export class AutocompleteField extends React.Component< - /** @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/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/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 558a7bac920b7..2db824f9c01d7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -163,17 +163,17 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams) align: 'right', }, { - name: averageTXLabel, - field: 'tx.avg', + name: averageRXLabel, + field: 'rx.avg', sortable: true, - render: (avg: number) => formatMetric('tx', avg), + render: (avg: number) => formatMetric('rx', avg), align: 'right', }, { - name: averageRXLabel, - field: 'rx.avg', + name: averageTXLabel, + field: 'tx.avg', sortable: true, - render: (avg: number) => formatMetric('rx', avg), + render: (avg: number) => formatMetric('tx', avg), align: 'right', }, { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx index 0e7d4e6d41e1b..ef2ca1e842ed4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx @@ -28,6 +28,7 @@ interface Props { value?: string | null; placeholder?: string; curryLoadSuggestions?: CurryLoadSuggestionsType; + compressed?: boolean; } function validateQuery(query: string) { @@ -46,6 +47,7 @@ export const MetricsExplorerKueryBar = ({ value, placeholder, curryLoadSuggestions = defaultCurryLoadSuggestions, + compressed, }: Props) => { const [draftQuery, setDraftQuery] = useState(value || ''); const [isValid, setValidation] = useState(true); @@ -81,6 +83,7 @@ export const MetricsExplorerKueryBar = ({ {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( { + it('flattens multi level item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + }, + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); + + it('does not flatten an array item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + item5: [1], + item6: { itemA: [1, 2, 3] }, + }, + key3: ['item7', 'item8'], + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + key3: ['item7', 'item8'], + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key2.item5': [1], + 'key2.item6.itemA': [1, 2, 3], + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index d59192306874a..01c69d4f5d126 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -226,18 +226,7 @@ export const shouldTermsAggOnContainer = (groupBy: string | string[] | undefined export const flattenAdditionalContext = ( additionalContext: AdditionalContext | undefined | null ): AdditionalContext => { - let flattenedContext: AdditionalContext = {}; - if (additionalContext) { - Object.keys(additionalContext).forEach((context: string) => { - if (additionalContext[context]) { - flattenedContext = { - ...flattenedContext, - ...flattenObject(additionalContext[context], [context + '.']), - }; - } - }); - } - return flattenedContext; + return additionalContext ? flattenObject(additionalContext) : {}; }; export const getContextForRecoveredAlerts = ( @@ -261,39 +250,24 @@ export const unflattenObject = (object: ob return acc; }, {} as T); -/** - * Wrap the key with [] if it is a key from an Array - * @param key The object key - * @param isArrayItem Flag to indicate if it is the key of an Array - */ -const renderKey = (key: string, isArrayItem: boolean): string => (isArrayItem ? `[${key}]` : key); - -export const flattenObject = ( - obj: AdditionalContext, - prefix: string[] = [], - isArrayItem = false -): AdditionalContext => - Object.keys(obj).reduce((acc, k) => { - const nextValue = obj[k]; - - if (typeof nextValue === 'object' && nextValue !== null) { - const isNextValueArray = Array.isArray(nextValue); - const dotSuffix = isNextValueArray ? '' : '.'; - - if (Object.keys(nextValue).length > 0) { - return { - ...acc, - ...flattenObject( - nextValue, - [...prefix, `${renderKey(k, isArrayItem)}${dotSuffix}`], - isNextValueArray - ), - }; +export const flattenObject = (obj: AdditionalContext, prefix: string = ''): AdditionalContext => + Object.keys(obj).reduce((acc, key) => { + const nextValue = obj[key]; + + if (nextValue) { + if (typeof nextValue === 'object' && !Array.isArray(nextValue)) { + const dotSuffix = '.'; + if (Object.keys(nextValue).length > 0) { + return { + ...acc, + ...flattenObject(nextValue, `${prefix}${key}${dotSuffix}`), + }; + } } - } - const fullPath = `${prefix.join('')}${renderKey(k, isArrayItem)}`; - acc[fullPath] = nextValue; + const fullPath = `${prefix}${key}`; + acc[fullPath] = nextValue; + } return acc; }, {}); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts new file mode 100644 index 0000000000000..63b0c81108a47 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -0,0 +1,301 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AlertInstanceContext as AlertContext, + AlertInstanceState as AlertState, +} from '@kbn/alerting-plugin/server'; +import { + AlertInstanceMock, + RuleExecutorServicesMock, + alertsMock, +} from '@kbn/alerting-plugin/server/mocks'; +import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import { createLifecycleRuleExecutorMock } from '@kbn/rule-registry-plugin/server/utils/create_lifecycle_rule_executor_mock'; +import { + Aggregators, + Comparator, + InventoryMetricConditions, +} from '../../../../common/alerting/metrics'; + +import type { LogMeta, Logger } from '@kbn/logging'; +import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { createInventoryMetricThresholdExecutor } from './inventory_metric_threshold_executor'; +import { ConditionResult } from './evaluate_condition'; +import { InfraBackendLibs } from '../../infra_types'; +import { infraPluginMock } from '../../../mocks'; + +jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} + +const persistAlertInstances = false; + +const fakeLogger = (msg: string, meta?: Meta) => {}; + +const logger = { + trace: fakeLogger, + debug: fakeLogger, + info: fakeLogger, + warn: fakeLogger, + error: fakeLogger, + fatal: fakeLogger, + log: () => void 0, + get: () => logger, +} as unknown as Logger; + +const mockOptions = { + executionId: '', + startedAt: new Date(), + previousStartedAt: null, + spaceId: '', + rule: { + id: '', + name: '', + tags: [], + consumer: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + producer: '', + ruleTypeId: '', + ruleTypeName: '', + muteAll: false, + }, + logger, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, +}; + +const setEvaluationResults = (response: Record) => { + jest.requireMock('./evaluate_condition').evaluateCondition.mockImplementation(() => response); +}; +const createMockStaticConfiguration = (sources: any) => ({ + alerting: { + inventory_threshold: { + group_by_page_size: 100, + }, + metric_threshold: { + group_by_page_size: 100, + }, + }, + inventory: { + compositeSize: 2000, + }, + sources, +}); + +const mockLibs = { + sources: { + getSourceConfiguration: (savedObjectsClient: any, sourceId: string) => { + return Promise.resolve({ + id: sourceId, + configuration: { + logIndices: { + type: 'index_pattern', + indexPatternId: 'some-id', + }, + }, + }); + }, + }, + getStartServices: () => [ + null, + infraPluginMock.createSetupContract(), + infraPluginMock.createStartContract(), + ], + configuration: createMockStaticConfiguration({}), + metricsRules: { + createLifecycleRuleExecutor: createLifecycleRuleExecutorMock, + }, + basePath: { + publicBaseUrl: 'http://localhost:5601', + prepend: (path: string) => path, + }, + logger, +} as unknown as InfraBackendLibs; + +const alertsServices = alertsMock.createRuleExecutorServices(); +const services: RuleExecutorServicesMock & + LifecycleAlertServices = { + ...alertsServices, + ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), +}; + +const alertInstances = new Map(); + +services.alertFactory.create.mockImplementation((instanceID: string) => { + const newAlertInstance: AlertTestInstance = { + instance: alertsMock.createAlertFactory.create(), + actionQueue: [], + state: {}, + }; + + const alertInstance: AlertTestInstance = persistAlertInstances + ? alertInstances.get(instanceID) || newAlertInstance + : newAlertInstance; + alertInstances.set(instanceID, alertInstance); + + alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + }); + + return alertInstance.instance; +}); + +function mostRecentAction(id: string) { + const instance = alertInstances.get(id); + if (!instance) return undefined; + return instance.actionQueue.pop(); +} + +function clearInstances() { + alertInstances.clear(); +} + +const executor = createInventoryMetricThresholdExecutor(mockLibs); + +const baseCriterion = { + aggType: Aggregators.AVERAGE, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as InventoryMetricConditions; + +describe('The inventory threshold alert type', () => { + describe('querying with Hosts and rule tags', () => { + afterAll(() => clearInstances()); + const execute = (comparator: Comparator, threshold: number[], state?: any) => + executor({ + ...mockOptions, + services, + params: { + nodeType: 'host', + criteria: [ + { + ...baseCriterion, + comparator, + threshold, + }, + ], + }, + state: state ?? {}, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('when tags are present in the source, rule tags and source tags are combined in alert context', async () => { + setEvaluationResults({ + 'host-01': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + + test('when tags are NOT present in the source, rule tags are added in alert context', async () => { + setEvaluationResults({ + 'host-01': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + cloud: undefined, + }, + }, + 'host-02': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: undefined, + }, + }, + }); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); +}); 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 ffd0a7e563339..e62fd9291fc33 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 @@ -76,176 +76,218 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = InventoryMetricThresholdAlertState, InventoryMetricThresholdAlertContext, InventoryMetricThresholdAllowedActionGroups - >(async ({ services, params, executionId, spaceId, startedAt, rule: { id: ruleId } }) => { - const startTime = Date.now(); + >( + async ({ + services, + params, + executionId, + spaceId, + startedAt, + rule: { id: ruleId, tags: ruleTags }, + }) => { + const startTime = Date.now(); - const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; + const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; - if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - const logger = createScopedLogger(libs.logger, 'inventoryRule', { - alertId: ruleId, - executionId, - }); - - const esClient = services.scopedClusterClient.asCurrentUser; - - const { - alertWithLifecycle, - savedObjectsClient, - getAlertStartedDate, - getAlertUuid, - getAlertByAlertUuid, - } = services; - const alertFactory: InventoryMetricThresholdAlertFactory = ( - id, - reason, - actionGroup, - additionalContext - ) => - alertWithLifecycle({ - id, - fields: { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - ...flattenAdditionalContext(additionalContext), - }, + const logger = createScopedLogger(libs.logger, 'inventoryRule', { + alertId: ruleId, + executionId, }); - if (!params.filterQuery && params.filterQueryText) { - try { - const { fromKueryExpression } = await import('@kbn/es-query'); - fromKueryExpression(params.filterQueryText); - } catch (e) { - logger.error(e.message); - const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able - const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); - const indexedStartedDate = - getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); - - alert.scheduleActions(actionGroupId, { - alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[AlertStates.ERROR], - group: UNGROUPED_FACTORY_KEY, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - reason, - timestamp: startedAt.toISOString(), - value: null, - viewInAppUrl: getViewInInventoryAppUrl({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedDate, - spaceId, - }), + const esClient = services.scopedClusterClient.asCurrentUser; + + const { + alertWithLifecycle, + savedObjectsClient, + getAlertStartedDate, + getAlertUuid, + getAlertByAlertUuid, + } = services; + const alertFactory: InventoryMetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext + ) => + alertWithLifecycle({ + id, + fields: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + ...flattenAdditionalContext(additionalContext), + }, }); - return { state: {} }; + if (!params.filterQuery && params.filterQueryText) { + try { + const { fromKueryExpression } = await import('@kbn/es-query'); + fromKueryExpression(params.filterQueryText); + } catch (e) { + logger.error(e.message); + const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able + const reason = buildInvalidQueryAlertReason(params.filterQueryText); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); + const indexedStartedDate = + getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + + alert.scheduleActions(actionGroupId, { + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + reason, + timestamp: startedAt.toISOString(), + value: null, + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + }); + + return { state: {} }; + } } - } - const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - - const [, , { logViews }] = await libs.getStartServices(); - const logQueryFields: LogQueryFields | undefined = await logViews - .getClient(savedObjectsClient, esClient) - .getResolvedLogView(sourceId) - .then( - ({ indices }) => ({ indexPattern: indices }), - () => undefined + const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); + + const [, , { logViews }] = await libs.getStartServices(); + const logQueryFields: LogQueryFields | undefined = await logViews + .getClient(savedObjectsClient, esClient) + .getResolvedLogView(sourceId) + .then( + ({ indices }) => ({ indexPattern: indices }), + () => undefined + ); + + const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; + const results = await Promise.all( + criteria.map((condition) => + evaluateCondition({ + compositeSize, + condition, + esClient, + executionTimestamp: startedAt, + filterQuery, + logger, + logQueryFields, + nodeType, + source, + }) + ) ); - const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; - const results = await Promise.all( - criteria.map((condition) => - evaluateCondition({ - compositeSize, - condition, - esClient, - executionTimestamp: startedAt, - filterQuery, - logger, - logQueryFields, - nodeType, - source, - }) - ) - ); - - let scheduledActionsCount = 0; - const inventoryItems = Object.keys(first(results)!); - for (const group of inventoryItems) { - // AND logic; all criteria must be across the threshold - const shouldAlertFire = results.every((result) => result[group]?.shouldFire); - const shouldAlertWarn = results.every((result) => result[group]?.shouldWarn); - // AND logic; because we need to evaluate all criteria, if one of them reports no data then the - // whole alert is in a No Data/Error state - const isNoData = results.some((result) => result[group]?.isNoData); - const isError = results.some((result) => result[group]?.isError); - - const nextState = isError - ? AlertStates.ERROR - : isNoData - ? AlertStates.NO_DATA - : shouldAlertFire - ? AlertStates.ALERT - : shouldAlertWarn - ? AlertStates.WARNING - : AlertStates.OK; - let reason; - if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { - reason = results - .map((result) => - buildReasonWithVerboseMetricName( - group, - result[group], - buildFiredAlertReason, - nextState === AlertStates.WARNING - ) - ) - .join('\n'); - } - if (alertOnNoData) { - if (nextState === AlertStates.NO_DATA) { - reason = results - .filter((result) => result[group].isNoData) - .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) - ) - .join('\n'); - } else if (nextState === AlertStates.ERROR) { + let scheduledActionsCount = 0; + const inventoryItems = Object.keys(first(results)!); + for (const group of inventoryItems) { + // AND logic; all criteria must be across the threshold + const shouldAlertFire = results.every((result) => result[group]?.shouldFire); + const shouldAlertWarn = results.every((result) => result[group]?.shouldWarn); + // AND logic; because we need to evaluate all criteria, if one of them reports no data then the + // whole alert is in a No Data/Error state + const isNoData = results.some((result) => result[group]?.isNoData); + const isError = results.some((result) => result[group]?.isError); + + const nextState = isError + ? AlertStates.ERROR + : isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING + : AlertStates.OK; + let reason; + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .filter((result) => result[group].isError) .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + buildReasonWithVerboseMetricName( + group, + result[group], + buildFiredAlertReason, + nextState === AlertStates.WARNING + ) ) .join('\n'); } + if (alertOnNoData) { + if (nextState === AlertStates.NO_DATA) { + reason = results + .filter((result) => result[group].isNoData) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) + ) + .join('\n'); + } else if (nextState === AlertStates.ERROR) { + reason = results + .filter((result) => result[group].isError) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + ) + .join('\n'); + } + } + if (reason) { + const actionGroupId = + nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; + + const additionalContext = results && results.length > 0 ? results[0][group].context : {}; + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...ruleTags]) + ); + + const alert = alertFactory(group, reason, actionGroupId, additionalContext); + const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(group); + + scheduledActionsCount++; + + const context = { + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[nextState], + group, + reason, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + timestamp: startedAt.toISOString(), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + value: mapToConditionsLookup(results, (result) => + formatMetric(result[group].metric, result[group].currentValue) + ), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + ...additionalContext, + }; + alert.scheduleActions(actionGroupId, context); + } } - if (reason) { - const actionGroupId = - nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; - const additionalContext = results && results.length > 0 ? results[0][group].context : null; + const { getRecoveredAlerts } = services.alertFactory.done(); + const recoveredAlerts = getRecoveredAlerts(); - const alert = alertFactory(group, reason, actionGroupId, additionalContext); - const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(group); + for (const alert of recoveredAlerts) { + const recoveredAlertId = alert.getId(); + const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(recoveredAlertId); + const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; + const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); - scheduledActionsCount++; - - const context = { + alert.setContext({ alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[nextState], - group, - reason, + alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, metric: mapToConditionsLookup(criteria, (c) => c.metric), - timestamp: startedAt.toISOString(), threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - value: mapToConditionsLookup(results, (result) => - formatMetric(result[group].metric, result[group].currentValue) - ), + timestamp: startedAt.toISOString(), viewInAppUrl: getViewInInventoryAppUrl({ basePath: libs.basePath, criteria, @@ -253,49 +295,19 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = timestamp: indexedStartedDate, spaceId, }), + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, ...additionalContext, - }; - alert.scheduleActions(actionGroupId, context); + }); } - } - const { getRecoveredAlerts } = services.alertFactory.done(); - const recoveredAlerts = getRecoveredAlerts(); - - for (const alert of recoveredAlerts) { - const recoveredAlertId = alert.getId(); - const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(recoveredAlertId); - const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; - const additionalContext = getContextForRecoveredAlerts(alertHits); - const originalActionGroup = getOriginalActionGroup(alertHits); - - alert.setContext({ - alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[AlertStates.OK], - group: recoveredAlertId, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - timestamp: startedAt.toISOString(), - viewInAppUrl: getViewInInventoryAppUrl({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedDate, - spaceId, - }), - originalAlertState: translateActionGroupToAlertState(originalActionGroup), - originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, - originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, - ...additionalContext, - }); - } + const stopTime = Date.now(); + logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); - const stopTime = Date.now(); - logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); - - return { state: {} }; - }); + return { state: {} }; + } + ); const formatThreshold = (metric: SnapshotMetricType, value: number | number[]) => { const metricFormatter = get(METRIC_FORMATTERS, metric, METRIC_FORMATTERS.count); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index a94521c22bde4..f363b5baeabf1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -690,6 +690,143 @@ describe('The metric threshold alert type', () => { }); }); + describe('querying with a groupBy parameter host.name and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['host.name'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('rule tags and source tags are combined in alert context', async () => { + setEvaluationResults([ + { + 'host-01': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-01' }, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-02' }, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + }); + + describe('querying without a groupBy parameter and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string = '', + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + test('rule tags are added in alert context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + const instanceID = '*'; + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); + describe('querying with multiple criteria', () => { afterAll(() => clearInstances()); const execute = ( diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index cd0b0f48f36d4..28a32a8c46174 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -287,9 +287,13 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) ? alertResults && alertResults.length > 0 - ? alertResults[0][group].context - : null - : null; + ? alertResults[0][group].context ?? {} + : {} + : {}; + + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) + ); const alert = alertFactory(`${group}`, reason, actionGroupId, additionalContext); const alertUuid = getAlertUuid(group); 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 ac69ae4f85ba5..5c20297b82c65 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,11 +5,12 @@ * 2.0. */ +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(), + getResolvedLogView: jest.fn((logViewId: string) => Promise.resolve(createResolvedLogViewMock())), putLogView: jest.fn(), resolveLogView: jest.fn(), }); diff --git a/x-pack/plugins/infra/server/services/rules/rule_data_client.ts b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts index 1435f4812d16c..3a81f957e9314 100644 --- a/x-pack/plugins/infra/server/services/rules/rule_data_client.ts +++ b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts @@ -6,11 +6,11 @@ */ import { CoreSetup, Logger } from '@kbn/core/server'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { InfraFeatureId } from '../../../common/constants'; import { RuleRegistrationContext, RulesServiceStartDeps } from './types'; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx index 72cca338117da..bc711e7d094c1 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx @@ -1520,7 +1520,10 @@ invalid: " operationDefinitionMap ) ).toEqual([ - 'A layer with only static values will not show results, use at least one dynamic metric', + { + message: + 'A layer with only static values will not show results, use at least one dynamic metric', + }, ]); }); @@ -1856,5 +1859,17 @@ invalid: " ).toEqual(undefined); } }); + + it('returns deduped errors on inner operation validation', () => { + expect( + formulaOperation.getErrorMessage!( + getNewLayerWithFormula('sum(clientip) + sum(clientip)', true), + 'col1', + indexPattern, + undefined, + operationDefinitionMap + ) + ).toHaveLength(1); + }); }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx index 70ccbc5fd25e8..20432dcdb6e24 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx @@ -7,7 +7,11 @@ import { i18n } from '@kbn/i18n'; import { uniqBy } from 'lodash'; -import type { BaseIndexPatternColumn, OperationDefinition } from '..'; +import type { + BaseIndexPatternColumn, + FieldBasedOperationErrorMessage, + OperationDefinition, +} from '..'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; import type { IndexPattern } from '../../../../../types'; import { runASTValidation, tryToParse } from './validation'; @@ -94,22 +98,30 @@ export const formulaOperation: OperationDefinition { - const def = visibleOperationsMap[col.operationType]; - if (def?.getErrorMessage) { - const messages = def.getErrorMessage( - layer, - id, - indexPattern, - dateRange, - visibleOperationsMap - ); - return messages ? { message: messages.join(', ') } : []; - } - return []; - }) - .filter(nonNullable); + const innerErrors = [ + ...managedColumns + .flatMap(([id, col]) => { + const def = visibleOperationsMap[col.operationType]; + if (def?.getErrorMessage) { + // TOOD: it would be nice to have nicer column names here rather than `Part of ` + const messages = def.getErrorMessage( + layer, + id, + indexPattern, + dateRange, + visibleOperationsMap + ); + return messages || []; + } + return []; + }) + .filter(nonNullable) + // dedup messages with the same content + .reduce((memo, message) => { + memo.add(message); + return memo; + }, new Set()), + ]; const hasBuckets = layer.columnOrder.some((colId) => layer.columns[colId].isBucketed); const hasOtherMetrics = layer.columnOrder.some((colId) => { const col = layer.columns[colId]; @@ -135,7 +147,7 @@ export const formulaOperation: OperationDefinition message) : undefined; + return innerErrors.length ? innerErrors : undefined; }, getPossibleOperation() { return { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts index a8828a9462ddd..520d0f53b143f 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts @@ -512,7 +512,7 @@ function checkMissingVariableOrFunctions( }, locations: missingVariables.map(({ location }) => location), }), - extraInfo: { missingFields: missingVariables.map(({ value }) => value) }, + extraInfo: { missingFields: [...new Set(missingVariables.map(({ value }) => value))] }, }); } const invalidVariableErrors = checkVariableEdgeCases( diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 39bc64adef207..edb3d82b09e13 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,11 @@ 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) => { this.onContainerStateChanged(input); - }) + } + ) ); } diff --git a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts index 188ddfccea0ba..94438f242ec8d 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts @@ -79,10 +79,10 @@ export const getColumnToLabelMap = ( return columnToLabel; }; -export const getSortedGroups = ( +export const getSortedAccessorsForGroup = ( datasource: DatasourcePublicAPI | undefined, layer: PieLayerState, - accessor: 'primaryGroups' | 'secondaryGroups' = 'primaryGroups' + accessor: 'primaryGroups' | 'secondaryGroups' | 'metrics' ) => { const originalOrder = datasource ?.getTableSpec() @@ -174,7 +174,9 @@ const generateCommonArguments = ( datasourceLayers: DatasourceLayers, paletteService: PaletteRegistry ) => { - const columnToLabelMap = getColumnToLabelMap(layer.metrics, datasourceLayers[layer.layerId]); + const datasource = datasourceLayers[layer.layerId]; + const columnToLabelMap = getColumnToLabelMap(layer.metrics, datasource); + const sortedMetricAccessors = getSortedAccessorsForGroup(datasource, layer, 'metrics'); return { labels: generateCommonLabelsAstArgs(state, attributes, layer, columnToLabelMap), @@ -182,7 +184,7 @@ const generateCommonArguments = ( .filter(({ columnId }) => !isCollapsed(columnId, layer)) .map(({ columnId }) => columnId) .map(prepareDimension), - metrics: (layer.allowMultipleMetrics ? layer.metrics : [layer.metrics[0]]).map( + metrics: (layer.allowMultipleMetrics ? sortedMetricAccessors : [sortedMetricAccessors[0]]).map( prepareDimension ), metricsToLabels: JSON.stringify(columnToLabelMap), @@ -290,16 +292,18 @@ function expressionHelper( const layer = state.layers[0]; const datasource = datasourceLayers[layer.layerId]; - const groups = Array.from( + const accessors = Array.from( new Set( [ - getSortedGroups(datasource, layer, 'primaryGroups'), - layer.secondaryGroups ? getSortedGroups(datasource, layer, 'secondaryGroups') : [], + getSortedAccessorsForGroup(datasource, layer, 'primaryGroups'), + layer.secondaryGroups + ? getSortedAccessorsForGroup(datasource, layer, 'secondaryGroups') + : [], ].flat() ) ); - const operations = groups + const operations = accessors .map((columnId) => ({ columnId, operation: datasource?.getOperationForColumnId(columnId) as Operation | null, @@ -323,11 +327,11 @@ function expressionHelper( type: 'expression', chain: [ ...(datasourceAst ? datasourceAst.chain : []), - ...groups + ...accessors .filter((columnId) => layer.collapseFns?.[columnId]) .map((columnId) => { return buildExpressionFunction('lens_collapse', { - by: groups.filter((chk) => chk !== columnId), + by: accessors.filter((chk) => chk !== columnId), metric: layer.metrics, fn: [layer.collapseFns![columnId]!], }).toAst(); diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts index 2bf830c0028cd..a11c1667f807f 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts @@ -448,119 +448,173 @@ describe('pie_visualization', () => { }); }); - it("doesn't count collapsed columns toward the dimension limits", () => { - const colIds = new Array(PartitionChartsMeta.pie.maxBuckets) - .fill(undefined) - .map((_, i) => String(i + 1)); - - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + it('orders metric accessors by datasource column order', () => { + const colIds = ['1', '2', '3', '4']; const state = getExampleState(); - state.layers[0].primaryGroups = colIds; - - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); - - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeFalsy(); + state.layers[0].metrics = colIds; + state.layers[0].allowMultipleMetrics = true; - const stateWithCollapsed = cloneDeep(state); - stateWithCollapsed.layers[0].collapseFns = { '1': 'sum' }; + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + // reverse the column IDs in the datasource + colIds.reverse().map((id) => ({ columnId: id, fields: [] })); + + // this is to make sure the accessors get sorted before palette colors are applied + const palette = paletteServiceMock.get('default'); + palette.getCategoricalColor + .mockReturnValueOnce('color 1') + .mockReturnValueOnce('color 2') + .mockReturnValueOnce('color 3') + .mockReturnValueOnce('color 4'); + + const config = pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }); - expect(findPrimaryGroup(getConfig(stateWithCollapsed))?.supportsMoreColumns).toBeTruthy(); + expect(findMetricGroup(config)?.accessors).toMatchInlineSnapshot(` + Array [ + Object { + "color": "color 1", + "columnId": "4", + "triggerIconType": "color", + }, + Object { + "color": "color 2", + "columnId": "3", + "triggerIconType": "color", + }, + Object { + "color": "color 3", + "columnId": "2", + "triggerIconType": "color", + }, + Object { + "color": "color 4", + "columnId": "1", + "triggerIconType": "color", + }, + ] + `); }); - it('counts multiple metrics toward the dimension limits when not mosaic', () => { - const colIds = new Array(PartitionChartsMeta.pie.maxBuckets - 1) - .fill(undefined) - .map((_, i) => String(i + 1)); + describe('dimension limits', () => { + it("doesn't count collapsed columns toward the dimension limits", () => { + const colIds = new Array(PartitionChartsMeta.pie.maxBuckets) + .fill(undefined) + .map((_, i) => String(i + 1)); - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); - const state = getExampleState(); - state.layers[0].primaryGroups = colIds; - state.layers[0].allowMultipleMetrics = true; + const state = getExampleState(); + state.layers[0].primaryGroups = colIds; - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); + const getConfig = (_state: PieVisualizationState) => + pieVisualization.getConfiguration({ + state: _state, + frame, + layerId: state.layers[0].layerId, + }); - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeFalsy(); - const stateWithMultipleMetrics = cloneDeep(state); - stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + const stateWithCollapsed = cloneDeep(state); + stateWithCollapsed.layers[0].collapseFns = { '1': 'sum' }; - expect( - findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns - ).toBeFalsy(); - }); + expect(findPrimaryGroup(getConfig(stateWithCollapsed))?.supportsMoreColumns).toBeTruthy(); + }); - it('does NOT count multiple metrics toward the dimension limits when mosaic', () => { - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => []; + it('counts multiple metrics toward the dimension limits when not mosaic', () => { + const colIds = new Array(PartitionChartsMeta.pie.maxBuckets - 1) + .fill(undefined) + .map((_, i) => String(i + 1)); - const state = getExampleState(); - state.shape = 'mosaic'; - state.layers[0].primaryGroups = []; - state.layers[0].allowMultipleMetrics = false; // always true for mosaic + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); + const state = getExampleState(); + state.layers[0].primaryGroups = colIds; + state.layers[0].allowMultipleMetrics = true; - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + const getConfig = (_state: PieVisualizationState) => + pieVisualization.getConfiguration({ + state: _state, + frame, + layerId: state.layers[0].layerId, + }); - const stateWithMultipleMetrics = cloneDeep(state); - stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); - expect( - findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns - ).toBeTruthy(); - }); + const stateWithMultipleMetrics = cloneDeep(state); + stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); - it('reports too many metric dimensions if multiple not enabled', () => { - const colIds = ['1', '2', '3', '4']; + expect( + findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns + ).toBeFalsy(); + }); - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + it('does NOT count multiple metrics toward the dimension limits when mosaic', () => { + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => []; - const state = getExampleState(); - state.layers[0].metrics = colIds; - state.layers[0].allowMultipleMetrics = false; - expect( - findMetricGroup( - pieVisualization.getConfiguration({ - state, - frame, - layerId: state.layers[0].layerId, - }) - )?.dimensionsTooMany - ).toBe(3); + const state = getExampleState(); + state.shape = 'mosaic'; + state.layers[0].primaryGroups = []; + state.layers[0].allowMultipleMetrics = false; // always true for mosaic - state.layers[0].allowMultipleMetrics = true; - expect( - findMetricGroup( + const getConfig = (_state: PieVisualizationState) => pieVisualization.getConfiguration({ - state, + state: _state, frame, layerId: state.layers[0].layerId, - }) - )?.dimensionsTooMany - ).toBe(0); + }); + + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + + const stateWithMultipleMetrics = cloneDeep(state); + stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + + expect( + findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns + ).toBeTruthy(); + }); + + it('reports too many metric dimensions if multiple not enabled', () => { + const colIds = ['1', '2', '3', '4']; + + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); + + const state = getExampleState(); + state.layers[0].metrics = colIds; + state.layers[0].allowMultipleMetrics = false; + expect( + findMetricGroup( + pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }) + )?.dimensionsTooMany + ).toBe(3); + + state.layers[0].allowMultipleMetrics = true; + expect( + findMetricGroup( + pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }) + )?.dimensionsTooMany + ).toBe(0); + }); }); it.each(Object.values(PieChartTypes).filter((type) => type !== 'mosaic'))( diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index cedfb12f72df7..a5c218a6483e1 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -29,7 +29,7 @@ import type { } from '../../types'; import { getColumnToLabelMap, - getSortedGroups, + getSortedAccessorsForGroup, toExpression, toPreviewExpression, } from './to_expression'; @@ -91,12 +91,13 @@ export const getDefaultColorForMultiMetricDimension = ({ datasource: DatasourcePublicAPI | undefined; }) => { const columnToLabelMap = datasource ? getColumnToLabelMap(layer.metrics, datasource) : {}; + const sortedMetrics = getSortedAccessorsForGroup(datasource, layer, 'metrics'); return paletteService.get('default').getCategoricalColor([ { name: columnToLabelMap[columnId], - rankAtDepth: layer.metrics.indexOf(columnId), - totalSeriesAtDepth: layer.metrics.length, + rankAtDepth: sortedMetrics.indexOf(columnId), + totalSeriesAtDepth: sortedMetrics.length, }, ]) as string; }; @@ -167,7 +168,7 @@ export const getPieVisualization = ({ const datasource = frame.datasourceLayers[layer.layerId]; const getPrimaryGroupConfig = (): VisualizationDimensionGroupConfig => { - const originalOrder = getSortedGroups(datasource, layer); + const originalOrder = getSortedAccessorsForGroup(datasource, layer, 'primaryGroups'); // When we add a column it could be empty, and therefore have no order const accessors = originalOrder.map((accessor) => ({ columnId: accessor, @@ -273,7 +274,11 @@ export const getPieVisualization = ({ }; const getSecondaryGroupConfig = (): VisualizationDimensionGroupConfig | undefined => { - const originalSecondaryOrder = getSortedGroups(datasource, layer, 'secondaryGroups'); + const originalSecondaryOrder = getSortedAccessorsForGroup( + datasource, + layer, + 'secondaryGroups' + ); const accessors = originalSecondaryOrder.map((accessor) => ({ columnId: accessor, triggerIconType: isCollapsed(accessor, layer) ? 'aggregate' : undefined, @@ -317,7 +322,11 @@ export const getPieVisualization = ({ const getMetricGroupConfig = (): VisualizationDimensionGroupConfig => { const hasSliceBy = layer.primaryGroups.length + (layer.secondaryGroups?.length ?? 0); - const accessors: AccessorConfig[] = layer.metrics.map((columnId, index) => ({ + const accessors: AccessorConfig[] = getSortedAccessorsForGroup( + datasource, + layer, + 'metrics' + ).map((columnId) => ({ columnId, ...(layer.allowMultipleMetrics ? hasSliceBy @@ -371,7 +380,7 @@ export const getPieVisualization = ({ }; }, - setDimension({ prevState, layerId, columnId, groupId }) { + setDimension({ prevState, layerId, columnId, groupId, previousColumn }) { return { ...prevState, layers: prevState.layers.map((l) => { @@ -393,7 +402,8 @@ export const getPieVisualization = ({ ], }; } - return { ...l, metrics: [...l.metrics.filter((metric) => metric !== columnId), columnId] }; + const metrics = [...l.metrics.filter((metric) => metric !== columnId), columnId]; + return { ...l, metrics }; }), }; }, 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/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/observability/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts index 1e24fbb120f53..13cde8e940e13 100644 --- a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts +++ b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts @@ -56,10 +56,7 @@ journey('Exploratory view', async ({ page, params }) => { }); step('Open exploratory view with monitor duration', async () => { - await Promise.all([ - page.waitForNavigation(TIMEOUT_60_SEC), - page.click('text=Explore data', TIMEOUT_60_SEC), - ]); + await page.waitForNavigation(TIMEOUT_60_SEC); await waitForLoadingToFinish({ page }); await page.click('text=browser', TIMEOUT_60_SEC); diff --git a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx index 95ec0d73aa6ae..0c53fae9af248 100644 --- a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx @@ -8,13 +8,14 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { BurnRateRuleParams } from '../../../typings'; import { BurnRateRuleEditor as Component } from './burn_rate_rule_editor'; export default { component: Component, title: 'app/SLO/BurnRateRule', - argTypes: {}, + decorators: [KibanaReactStorybookDecorator], }; const Template: ComponentStory = () => ( diff --git a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx index fd06b1fef6ab5..1debffc408049 100644 --- a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx +++ b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx @@ -9,11 +9,13 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { SLOResponse } from '@kbn/slo-schema'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { SloSelector as Component } from './slo_selector'; export default { component: Component, title: 'app/SLO/BurnRateRule', + decorators: [KibanaReactStorybookDecorator], }; const Template: ComponentStory = () => ( diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx index 4d154504ce6ea..aec17127b8dd9 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx @@ -62,7 +62,7 @@ export function ObservabilityAlertSearchBar({ const onSearchBarParamsChange = useCallback< (query: { dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' }; - query: string; + query?: string; }) => void >( ({ dateRange, query }) => { @@ -76,7 +76,7 @@ export function ObservabilityAlertSearchBar({ query, [...getAlertStatusQuery(status), ...defaultSearchQueries] ); - onKueryChange(query); + if (query) onKueryChange(query); timeFilterService.setTime(dateRange); onRangeFromChange(dateRange.from); onRangeToChange(dateRange.to); diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts similarity index 66% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts rename to x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts index 9e4c0ef21fdea..f34d7a4a7070b 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts @@ -5,5 +5,9 @@ * 2.0. */ -export { IndexPatternStatsCollector } from './index_pattern_stats_collector'; -export type { IndexPatternStats } from './types'; +export function useCapabilities() { + return { + hasReadCapabilities: true, + hasWriteCapabilities: true, + }; +} diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts new file mode 100644 index 0000000000000..f493eadac3806 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UseFetchActiveAlerts } from '../use_fetch_active_alerts'; + +export const useFetchActiveAlerts = ({ + sloIds = [], +}: { + sloIds: string[]; +}): UseFetchActiveAlerts => { + return { + isLoading: false, + isSuccess: false, + isError: false, + data: sloIds.reduce( + (acc, sloId, index) => ({ + ...acc, + ...(index % 2 === 0 && { [sloId]: { count: 2, ruleIds: ['rule-1', 'rule-2'] } }), + }), + {} + ), + }; +}; 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 f869e6da47c64..fe10d84c4f428 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 @@ -23,6 +23,5 @@ export const useFetchHistoricalSummary = ({ isSuccess: false, isError: false, sloHistoricalSummaryResponse: data, - refetch: function () {} as UseFetchHistoricalSummaryResponse['refetch'], }; }; diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts new file mode 100644 index 0000000000000..4e90e51060df0 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.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 { useQuery } from '@tanstack/react-query'; +import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; + +import { useKibana } from '../../utils/kibana_react'; + +type SloId = string; + +interface Params { + sloIds: SloId[]; +} + +export interface ActiveAlerts { + count: number; + ruleIds: string[]; +} + +type ActiveAlertsMap = Record; + +export interface UseFetchActiveAlerts { + data: ActiveAlertsMap; + isLoading: boolean; + isSuccess: boolean; + isError: boolean; +} + +interface FindApiResponse { + aggregations: { + perSloId: { + buckets: Array<{ + key: string; + doc_count: number; + perRuleId: { buckets: Array<{ key: string; doc_count: number }> }; + }>; + }; + }; +} + +const EMPTY_ACTIVE_ALERTS_MAP = {}; + +export function useFetchActiveAlerts({ sloIds = [] }: Params): UseFetchActiveAlerts { + const { http } = useKibana().services; + + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + queryKey: ['fetchActiveAlerts', sloIds], + queryFn: async ({ signal }) => { + try { + const response = await http.post(`${BASE_RAC_ALERTS_API_PATH}/find`, { + body: JSON.stringify({ + feature_ids: ['slo'], + size: 0, + query: { + bool: { + filter: [ + { + term: { + 'kibana.alert.rule.rule_type_id': 'slo.rules.burnRate', + }, + }, + { + term: { + 'kibana.alert.status': 'active', + }, + }, + ], + }, + }, + aggs: { + perSloId: { + terms: { + field: 'kibana.alert.rule.parameters.sloId', + }, + aggs: { + perRuleId: { + terms: { + field: 'kibana.alert.rule.uuid', + }, + }, + }, + }, + }, + }), + signal, + }); + + return response.aggregations.perSloId.buckets.reduce( + (acc, bucket) => ({ + ...acc, + [bucket.key]: { + count: bucket.doc_count ?? 0, + ruleIds: bucket.perRuleId.buckets.map((rule) => rule.key), + } as ActiveAlerts, + }), + {} + ); + } catch (error) { + // ignore error + } + }, + refetchOnWindowFocus: false, + }); + + return { + data: isInitialLoading ? EMPTY_ACTIVE_ALERTS_MAP : data ?? EMPTY_ACTIVE_ALERTS_MAP, + isLoading: isInitialLoading || isLoading || isRefetching, + isSuccess, + isError, + }; +} diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts index cc7bdb1193d63..63ed913902ee8 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts @@ -11,6 +11,7 @@ import moment from 'moment'; import { useKibana } from '../../utils/kibana_react'; export type Suggestion = string; + export interface UseFetchApmSuggestions { suggestions: Suggestion[]; isLoading: boolean; @@ -28,7 +29,7 @@ interface ApiResponse { terms: string[]; } -const EMPTY_RESPONSE: ApiResponse = { terms: [] }; +const NO_SUGGESTIONS: Suggestion[] = []; export function useFetchApmSuggestions({ fieldName, @@ -61,7 +62,7 @@ export function useFetchApmSuggestions({ }); return { - suggestions: isInitialLoading ? EMPTY_RESPONSE.terms : data ?? EMPTY_RESPONSE.terms, + suggestions: isInitialLoading ? NO_SUGGESTIONS : data ?? NO_SUGGESTIONS, isLoading: isInitialLoading || isLoading || isRefetching, isSuccess, isError, 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 e1bca23188295..2037c97df52c1 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 @@ -5,12 +5,7 @@ * 2.0. */ -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, - useQuery, -} from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { FetchHistoricalSummaryResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; @@ -22,9 +17,6 @@ export interface UseFetchHistoricalSummaryResponse { isLoading: boolean; isSuccess: boolean; isError: boolean; - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; } export interface Params { @@ -36,33 +28,30 @@ export function useFetchHistoricalSummary({ }: Params): UseFetchHistoricalSummaryResponse { const { http } = useKibana().services; - const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( - { - queryKey: ['fetchHistoricalSummary', sloIds], - queryFn: async ({ signal }) => { - try { - const response = await http.post( - '/internal/observability/slos/_historical_summary', - { - body: JSON.stringify({ sloIds }), - signal, - } - ); - - return response; - } catch (error) { - // ignore error for retrieving slos - } - }, - refetchOnWindowFocus: false, - } - ); + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + queryKey: ['fetchHistoricalSummary', sloIds], + queryFn: async ({ signal }) => { + try { + const response = await http.post( + '/internal/observability/slos/_historical_summary', + { + body: JSON.stringify({ sloIds }), + signal, + } + ); + + return response; + } catch (error) { + // ignore error + } + }, + refetchOnWindowFocus: false, + }); return { sloHistoricalSummaryResponse: isInitialLoading ? EMPTY_RESPONSE : data ?? EMPTY_RESPONSE, isLoading: isInitialLoading || isLoading || isRefetching, isSuccess, isError, - refetch, }; } 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 7864b85181a3f..648efb5481d31 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 @@ -11,8 +11,8 @@ import { RefetchQueryFilters, useQuery, } from '@tanstack/react-query'; - import { FindSLOResponse } from '@kbn/slo-schema'; + import { useKibana } from '../../utils/kibana_react'; interface SLOListParams { @@ -61,7 +61,7 @@ export function useFetchSloList({ return response; } catch (error) { - // ignore error for retrieving slos + // ignore error } }, refetchOnWindowFocus: false, 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 c24bf74dc6346..e61e1c7056596 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 @@ -18,10 +18,11 @@ import { i18n } from '@kbn/i18n'; import type { CreateSLOInput } from '@kbn/slo-schema'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; -import { FieldSelector } from '../common/field_selector'; +import { FieldSelector } from '../apm_common/field_selector'; +import { QueryBuilder } from '../common/query_builder'; export function ApmAvailabilityIndicatorTypeForm() { - const { control, setValue } = useFormContext(); + const { control, setValue, watch } = useFormContext(); const { data: apmIndex } = useFetchApmIndex(); useEffect(() => { setValue('indicator.params.index', apmIndex); @@ -145,7 +146,23 @@ export function ApmAvailabilityIndicatorTypeForm() { )} />
      - + + +
      ); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx similarity index 95% rename from x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx index 748b53ca82d57..47111ee1c565b 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx @@ -15,7 +15,7 @@ import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../../constants'; export default { component: Component, - title: 'app/SLO/EditPage/Common/FieldSelector', + title: 'app/SLO/EditPage/ApmCommon/FieldSelector', decorators: [KibanaReactStorybookDecorator], }; diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.tsx diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx index 4dcf095571c4a..a5f39a0f8f958 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx @@ -12,10 +12,11 @@ import { i18n } from '@kbn/i18n'; import type { CreateSLOInput } from '@kbn/slo-schema'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; -import { FieldSelector } from '../common/field_selector'; +import { FieldSelector } from '../apm_common/field_selector'; +import { QueryBuilder } from '../common/query_builder'; export function ApmLatencyIndicatorTypeForm() { - const { control, setValue } = useFormContext(); + const { control, setValue, watch } = useFormContext(); const { data: apmIndex } = useFetchApmIndex(); useEffect(() => { setValue('indicator.params.index', apmIndex); @@ -113,7 +114,23 @@ export function ApmLatencyIndicatorTypeForm() { )} /> - + + +
      ); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.stories.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.stories.tsx diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx 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 d9c39b0715be4..a9fb7cd58e480 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 @@ -12,7 +12,7 @@ import { useFormContext } from 'react-hook-form'; import { CreateSLOInput } from '@kbn/slo-schema'; import { IndexSelection } from './index_selection'; -import { QueryBuilder } from './query_builder'; +import { QueryBuilder } from '../common/query_builder'; export function CustomKqlIndicatorTypeForm() { const { control, watch } = useFormContext(); 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 32cdeb785270e..cd4c7d9c2d5d5 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 @@ -6,20 +6,38 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +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 { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloTimeWindowBadge } from './slo_time_window_badge'; export interface Props { slo: SLOWithSummaryResponse; + activeAlerts?: ActiveAlerts; } -export function SloBadges({ slo }: Props) { +export function SloBadges({ slo, activeAlerts }: Props) { + const { + application: { navigateToUrl }, + http: { basePath }, + } = useKibana().services; + + const handleClick = () => { + if (activeAlerts) { + navigateToUrl( + `${basePath.prepend(paths.observability.alerts)}?_a=${toAlertsPageQuery(activeAlerts)}` + ); + } + }; + return ( - + @@ -27,6 +45,34 @@ export function SloBadges({ slo }: Props) { + {!!activeAlerts && ( + + + {i18n.translate('xpack.observability.slos.slo.activeAlertsBadge.label', { + defaultMessage: '{count, plural, one {# alert} other {# alerts}}', + values: { count: activeAlerts.count }, + })} + + + )} ); } + +function toAlertsPageQuery(activeAlerts: ActiveAlerts): string { + const kuery = activeAlerts.ruleIds + .map((ruleId) => `kibana.alert.rule.uuid:"${activeAlerts.ruleIds[0]}"`) + .join(' or '); + + const query = `(kuery:'${kuery}',rangeFrom:now-15m,rangeTo:now,status:all)`; + return query; +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx index a1c89ca857b18..ed69ebae221e5 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { EuiBadge, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { euiLightVars } from '@kbn/ui-theme'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; export interface SloStatusProps { @@ -21,7 +20,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) {
      {slo.summary.status === 'NO_DATA' && ( - + {i18n.translate('xpack.observability.slos.slo.state.noData', { defaultMessage: 'No data', })} @@ -29,7 +28,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'HEALTHY' && ( - + {i18n.translate('xpack.observability.slos.slo.state.healthy', { defaultMessage: 'Healthy', })} @@ -37,7 +36,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'DEGRADING' && ( - + {i18n.translate('xpack.observability.slos.slo.state.degrading', { defaultMessage: 'Degrading', })} @@ -45,7 +44,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'VIOLATED' && ( - + {i18n.translate('xpack.observability.slos.slo.state.violated', { defaultMessage: 'Violated', })} @@ -56,7 +55,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { {slo.summary.errorBudget.isEstimated && (
      - + {i18n.translate('xpack.observability.slos.slo.state.forecasted', { defaultMessage: 'Forecasted', })} 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 a56d48698d540..282b82bf41b7e 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 @@ -20,6 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useKibana } from '../../../utils/kibana_react'; import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; @@ -36,12 +37,14 @@ export interface SloListItemProps { slo: SLOWithSummaryResponse; historicalSummary?: HistoricalSummaryResponse[]; historicalSummaryLoading: boolean; + activeAlerts?: ActiveAlerts; } export function SloListItem({ slo, historicalSummary = [], historicalSummaryLoading, + activeAlerts, }: SloListItemProps) { const { application: { navigateToUrl }, @@ -101,7 +104,7 @@ export function SloListItem({ {slo.name} - + 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 2e0bb5b267b7a..85ce283c90b18 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 @@ -8,6 +8,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; import { SloListItem } from './slo_list_item'; import { SloListEmpty } from './slo_list_empty'; @@ -23,6 +24,10 @@ export function SloListItems({ sloList, loading, error }: Props) { const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } = useFetchHistoricalSummary({ sloIds: sloList.map((slo) => slo.id) }); + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ + sloIds: sloList.map((slo) => slo.id), + }); + if (!loading && !error && sloList.length === 0) { return ; } @@ -38,6 +43,7 @@ export function SloListItems({ sloList, loading, error }: Props) { slo={slo} historicalSummary={sloHistoricalSummaryResponse[slo.id]} historicalSummaryLoading={historicalSummaryLoading} + activeAlerts={activeAlertsBySlo[slo.id]} /> ))} diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 98b21abbaefe5..39a69d7fa1666 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -18,10 +18,10 @@ import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plu import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; import { createUICapabilities } from '@kbn/cases-plugin/common'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { kubernetesGuideId, diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.ts b/x-pack/plugins/observability/server/services/slo/update_slo.ts index 9b4aa4e7dc213..cf2c0e2e1c89f 100644 --- a/x-pack/plugins/observability/server/services/slo/update_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/update_slo.ts @@ -6,7 +6,6 @@ */ import deepEqual from 'fast-deep-equal'; -import merge from 'lodash/merge'; import { ElasticsearchClient } from '@kbn/core/server'; import { UpdateSLOParams, UpdateSLOResponse, updateSLOResponseSchema } from '@kbn/slo-schema'; @@ -42,7 +41,7 @@ export class UpdateSLO { private updateSLO(originalSlo: SLO, params: UpdateSLOParams) { let hasBreakingChange = false; - const updatedSlo: SLO = merge({}, originalSlo, params, { updatedAt: new Date() }); + const updatedSlo: SLO = Object.assign({}, originalSlo, params, { updatedAt: new Date() }); validateSLO(updatedSlo); if (!deepEqual(originalSlo.indicator, updatedSlo.indicator)) { diff --git a/x-pack/plugins/osquery/common/utils/replace_params_query.ts b/x-pack/plugins/osquery/common/utils/replace_params_query.ts index 3c99daf5ec3cf..d06be33a87330 100644 --- a/x-pack/plugins/osquery/common/utils/replace_params_query.ts +++ b/x-pack/plugins/osquery/common/utils/replace_params_query.ts @@ -10,6 +10,10 @@ import { each, get } from 'lodash'; const CONTAINS_DYNAMIC_PARAMETER_REGEX = /\{{([^}]+)\}}/g; // when there are 2 opening and 2 closing curly brackets (including brackets) export const replaceParamsQuery = (query: string, data: object) => { + if (!containsDynamicQuery(query)) { + return { result: query, skipped: false }; + } + const matchedBrackets = query.match(new RegExp(CONTAINS_DYNAMIC_PARAMETER_REGEX)); let resultQuery = query; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index 35c5c2aa15503..aaf5fc319be15 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -16,7 +16,7 @@ import { addIntegration, closeModalIfVisible, closeToastIfVisible } from '../../ import { login } from '../../tasks/login'; import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query'; import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; -import { DEFAULT_POLICY } from '../../screens/fleet'; +import { DEFAULT_POLICY, OSQUERY_POLICY } from '../../screens/fleet'; describe('ALL - Add Integration', () => { const integration = 'Osquery Manager'; @@ -43,11 +43,10 @@ describe('ALL - Add Integration', () => { cy.get('[title="Osquery Manager • Integration"]').should('exist').click(); }); - it('should add the old integration and be able to upgrade it', () => { + it.skip('should add the old integration and be able to upgrade it', () => { const oldVersion = '0.7.4'; cy.visit(OLD_OSQUERY_MANAGER); - cy.contains(integration).click(); addIntegration(); cy.contains('osquery_manager-1'); cy.visit('app/fleet/policies'); @@ -94,11 +93,19 @@ describe('ALL - Add Integration', () => { addIntegration(); cy.contains('osquery_manager-'); closeToastIfVisible(); - cy.getBySel('nav-search-input').type('Osquery'); - cy.get('[title="Osquery • Management"]').should('exist').click(); + cy.visit(OSQUERY); cy.contains('Live queries history'); }); + it(`add integration to ${OSQUERY_POLICY}`, () => { + cy.visit(FLEET_AGENT_POLICIES); + cy.contains(OSQUERY_POLICY).click(); + cy.contains('Add integration').click(); + cy.contains(integration).click(); + addIntegration(OSQUERY_POLICY); + cy.contains('osquery_manager-'); + }); + it('should have integration and packs copied when upgrading integration', () => { const packageName = 'osquery_manager'; const oldVersion = '1.2.0'; @@ -110,12 +117,12 @@ describe('ALL - Add Integration', () => { cy.contains('Upgrade'); cy.contains('Agent policy 1').click(); cy.get('tr') - .should('contain', 'osquery_manager-2') + .should('contain', 'osquery_manager-3') .and('contain', 'Osquery Manager') .and('contain', `v${oldVersion}`); cy.contains('Actions').click(); cy.contains('View policy').click(); - cy.contains('name: osquery_manager-2'); + cy.contains('name: osquery_manager-3'); cy.contains(`version: ${oldVersion}`); cy.get('.euiFlyoutFooter').within(() => { cy.contains('Close').click(); @@ -142,19 +149,19 @@ describe('ALL - Add Integration', () => { cy.contains(/^Advanced$/).click(); cy.contains('"Integration":'); cy.contains(/^Upgrade integration$/).click(); - cy.contains(/^osquery_manager-2$/).click(); + cy.contains(/^osquery_manager-3$/).click(); cy.contains(/^Advanced$/).click(); cy.contains('"Integration":'); cy.contains('Cancel').click(); closeModalIfVisible(); cy.get('tr') - .should('contain', 'osquery_manager-2') + .should('contain', 'osquery_manager-3') .and('contain', 'Osquery Manager') .and('contain', 'v') .and('not.contain', `v${oldVersion}`); cy.contains('Actions').click(); cy.contains('View policy').click(); - cy.contains('name: osquery_manager-2'); + cy.contains('name: osquery_manager-3'); // test list of prebuilt queries navigateTo('/app/osquery/saved_queries'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts index 416fdfe5b971a..2f313d1bb9979 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts @@ -58,7 +58,7 @@ describe('Alert Event Details', () => { it('should prepare packs and alert rules', () => { const PACK_NAME = 'testpack'; - navigateTo('/app/osquery/packs'); + navigateTo('/app/osquery/live_queries'); preparePack(PACK_NAME); findAndClickButton('Edit'); cy.contains(`Edit ${PACK_NAME}`); @@ -439,6 +439,27 @@ describe('Alert Event Details', () => { }); }); + it('should be able to run take action query against all enrolled agents', () => { + loadAlertsEvents(); + cy.getBySel('expand-event').first().click({ force: true }); + cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('osquery-action-item').click(); + cy.getBySel('agentSelection').within(() => { + cy.getBySel('comboBoxClearButton').click(); + cy.getBySel('comboBoxInput').type('All{downArrow}{enter}{esc}'); + cy.contains('All agents'); + }); + inputQuery("SELECT * FROM os_version where name='{{host.os.name}}';", { + parseSpecialCharSequences: false, + }); + cy.wait(1000); + submitQuery(); + cy.getBySel('flyout-body-osquery').within(() => { + // at least 2 agents should have responded + cy.get('[data-grid-row-index]').should('have.length.at.least', 2); + }); + }); + it('should substitute params in osquery ran from timelines alerts', () => { loadAlertsEvents(); cy.getBySel('send-alert-to-timeline-button').first().click({ force: true }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts index e58f6c689eb55..3aaeff293ac47 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts @@ -29,6 +29,7 @@ describe('ALL - Custom space', () => { id: CUSTOM_SPACE, name: CUSTOM_SPACE, }, + failOnStatusCode: false, headers: { 'kbn-xsrf': 'create-space' }, }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts index 8991858bd7c26..aa21b6142a9df 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts @@ -68,6 +68,7 @@ describe('ALL - Live Query', () => { checkResults(); cy.react('Cell', { props: { columnIndex: 0 } }) .should('exist') + .first() .click(); cy.url().should('include', 'app/fleet/agents/'); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts index 0edefdc24ea42..998d5220e24f4 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts @@ -29,19 +29,20 @@ describe('ALL - Inventory', () => { cy.wait(1000); - cy.getBySel('nodeContainer').click(); + cy.getBySel('nodeContainer').first().click(); cy.contains('Osquery').click(); inputQuery('select * from uptime;'); submitQuery(); checkResults(); }); + it('should be able to run the previously saved query', () => { cy.getBySel('toggleNavButton').click(); cy.getBySel('collapsibleNavAppLink').contains('Infrastructure').click(); cy.wait(500); - cy.getBySel('nodeContainer').click(); + cy.getBySel('nodeContainer').first().click(); cy.contains('Osquery').click(); cy.getBySel('comboBoxInput').first().click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts index d402412dcdbd7..74253324acdfb 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts @@ -476,9 +476,8 @@ describe('ALL - Packs', () => { navigateTo('/app/osquery/packs'); }); - it('add global packs to polciies', () => { + it('add global packs to policies', () => { const globalPack = 'globalPack'; - cy.contains('Packs').click(); findAndClickButton('Add pack'); findFormFieldByRowsLabelAndType('Name', globalPack); cy.getBySel('policyIdsComboBox').should('exist'); @@ -517,9 +516,9 @@ describe('ALL - Packs', () => { cy.contains('rev. 2').click(); }); }); + it('add proper shard to policies packs config', () => { const shardPack = 'shardPack'; - cy.contains('Packs').click(); cy.getBySel('pagination-button-next').click(); findAndClickButton('Add pack'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 9605984969c00..8af212661f741 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -64,10 +64,12 @@ describe('ALL - Saved queries', () => { cy.contains('Snapshot'); }); }); + describe('prebuilt ', () => { before(() => { runKbnArchiverScript(ArchiverMethod.LOAD, 'pack_with_prebuilt_saved_queries'); }); + beforeEach(() => { navigateTo('/app/osquery/saved_queries'); }); @@ -77,7 +79,6 @@ describe('ALL - Saved queries', () => { }); it('checks result type on prebuilt saved query', () => { - cy.contains('Saved queries').click(); cy.react('CustomItemAction', { props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, }).click(); @@ -85,6 +86,7 @@ describe('ALL - Saved queries', () => { cy.contains('Snapshot'); }); }); + it('user can run prebuilt saved query and add to case', () => { cy.react('PlayButtonComponent', { props: { savedQuery: { attributes: { id: 'users_elastic' } } }, @@ -98,7 +100,6 @@ describe('ALL - Saved queries', () => { }); it('user cant delete prebuilt saved query', () => { - cy.contains('Saved queries').click(); cy.react('CustomItemAction', { props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, }).click(); @@ -114,6 +115,7 @@ describe('ALL - Saved queries', () => { }).click(); deleteAndConfirm('query'); }); + it('user can edit prebuilt saved query under pack', () => { const PACK_NAME = 'pack_with_prebuilt_sq'; preparePack(PACK_NAME); diff --git a/x-pack/plugins/osquery/cypress/screens/fleet.ts b/x-pack/plugins/osquery/cypress/screens/fleet.ts index b7cce6484c405..3f5370126f95f 100644 --- a/x-pack/plugins/osquery/cypress/screens/fleet.ts +++ b/x-pack/plugins/osquery/cypress/screens/fleet.ts @@ -10,3 +10,4 @@ export const ADD_AGENT_BUTTON = 'addAgentButton'; export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab'; export const ENROLLMENT_TOKENS_TAB = 'fleet-enrollment-tokens-tab'; export const DEFAULT_POLICY = 'Default Fleet Server policy'; +export const OSQUERY_POLICY = 'Osquery policy'; diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index 5e1c474da2d78..c37dbc7766bf4 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -17,7 +17,7 @@ export const selectAllAgents = () => { }).click(); cy.react('EuiFilterSelectItem').contains('All agents').should('exist'); cy.react('AgentsTable EuiComboBox').type('{downArrow}{enter}{esc}'); - cy.contains('1 agent selected.'); + cy.contains('2 agents selected.'); }; export const clearInputQuery = () => diff --git a/x-pack/plugins/osquery/cypress/tasks/navigation.ts b/x-pack/plugins/osquery/cypress/tasks/navigation.ts index 7b1505eecd698..9cbd0fac297a2 100644 --- a/x-pack/plugins/osquery/cypress/tasks/navigation.ts +++ b/x-pack/plugins/osquery/cypress/tasks/navigation.ts @@ -6,6 +6,7 @@ */ import { TOGGLE_NAVIGATION_BTN } from '../screens/navigation'; +import { closeToastIfVisible } from './integrations'; export const INTEGRATIONS = 'app/integrations#/'; export const FLEET = 'app/fleet/'; @@ -20,7 +21,7 @@ export const navigateTo = (page: string, opts?: Partial) = cy.contains('Loading Elastic').should('not.exist'); // There's a security warning toast that seemingly makes ui elements in the bottom right unavailable, so we close it - cy.get('[data-test-subj="toastCloseButton"]', { timeout: 30000 }).click(); + closeToastIfVisible(); cy.waitForReact(); }; diff --git a/x-pack/plugins/osquery/cypress/tasks/packs.ts b/x-pack/plugins/osquery/cypress/tasks/packs.ts index 6d74ac2110a0c..80f9a497ef6b4 100644 --- a/x-pack/plugins/osquery/cypress/tasks/packs.ts +++ b/x-pack/plugins/osquery/cypress/tasks/packs.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { closeModalIfVisible } from './integrations'; +import { closeModalIfVisible, closeToastIfVisible } from './integrations'; export const preparePack = (packName: string) => { cy.contains('Packs').click(); @@ -21,7 +21,7 @@ export const deactivatePack = (packName: string) => { cy.contains(`Successfully deactivated "${packName}" pack`).should('not.exist'); cy.contains(`Successfully deactivated "${packName}" pack`).should('exist'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); }; export const activatePack = (packName: string) => { @@ -32,5 +32,5 @@ export const activatePack = (packName: string) => { cy.contains(`Successfully activated "${packName}" pack`).should('not.exist'); cy.contains(`Successfully activated "${packName}" pack`).should('exist'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); }; diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts index 7c70d8cdb3a2b..780880b838caf 100644 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts @@ -6,6 +6,7 @@ */ import { RESULTS_TABLE_BUTTON } from '../screens/live_query'; +import { closeToastIfVisible } from './integrations'; import { checkResults, BIG_QUERY, @@ -15,6 +16,7 @@ import { selectAllAgents, submitQuery, } from './live_query'; +import { navigateTo } from './navigation'; export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescription: string) => it( @@ -71,7 +73,7 @@ export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescr // visit Status results cy.react('EuiTab', { props: { id: 'status' } }).click(); - cy.react('EuiTableRow').should('have.lengthOf', 1); + cy.react('EuiTableRow').should('have.lengthOf', 2); // save new query cy.contains('Exit full screen').should('not.exist'); @@ -81,10 +83,10 @@ export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescr findFormFieldByRowsLabelAndType('Description (optional)', savedQueryDescription); cy.react('EuiButtonDisplay').contains('Save').click(); cy.contains('Successfully saved'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); // play saved query - cy.contains('Saved queries').click(); + navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryId); cy.react('PlayButtonComponent', { props: { savedQuery: { attributes: { id: savedQueryId } } }, diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts index 4974fc21b440b..b2f6ca09234eb 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts @@ -11,11 +11,7 @@ import { filter, flatten, isEmpty, map, omit, pick, pickBy, some } from 'lodash' import { AGENT_ACTIONS_INDEX } from '@kbn/fleet-plugin/common'; import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; -import { - containsDynamicQuery, - replaceParamsQuery, -} from '../../../common/utils/replace_params_query'; -import { createDynamicQueries, createQueries } from './create_queries'; +import { createDynamicQueries, replacedQueries } from './create_queries'; import { getInternalSavedObjectsClient } from '../../routes/utils'; import { parseAgentSelection } from '../../lib/parse_agent_groups'; import { packSavedObjectType } from '../../../common/types'; @@ -94,16 +90,13 @@ export const createActionHandler = async ( : undefined, queries: packSO ? map(convertSOQueriesToPack(packSO.attributes.queries), (packQuery, packQueryId) => { - const dynamicQueryPresent = packQuery.query && containsDynamicQuery(packQuery.query); + const replacedQuery = replacedQueries(packQuery.query, alertData); return pickBy( { action_id: uuidv4(), id: packQueryId, - query: - dynamicQueryPresent && alertData - ? replaceParamsQuery(packQuery.query, alertData).result - : packQuery.query, + ...replacedQuery, ecs_mapping: packQuery.ecs_mapping, version: packQuery.version, platform: packQuery.platform, @@ -112,9 +105,7 @@ export const createActionHandler = async ( (value) => !isEmpty(value) ); }) - : alertData - ? await createDynamicQueries(params, alertData, osqueryContext) - : await createQueries(params, selectedAgents, osqueryContext), + : await createDynamicQueries({ params, alertData, agents: selectedAgents, osqueryContext }), }; const fleetActions = map( @@ -130,31 +121,33 @@ export const createActionHandler = async ( data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']), }) ); + if (fleetActions.length) { + await esClientInternal.bulk({ + refresh: 'wait_for', + body: flatten( + fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) + ), + }); - await esClientInternal.bulk({ - refresh: 'wait_for', - body: flatten( - fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) - ), - }); + const actionsComponentTemplateExists = await esClientInternal.indices.exists({ + index: `${ACTIONS_INDEX}*`, + }); - const actionsComponentTemplateExists = await esClientInternal.indices.exists({ - index: `${ACTIONS_INDEX}*`, - }); + if (actionsComponentTemplateExists) { + await esClientInternal.bulk({ + refresh: 'wait_for', + body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], + }); + } - if (actionsComponentTemplateExists) { - await esClientInternal.bulk({ - refresh: 'wait_for', - body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], + osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { + ...omit(osqueryAction, ['type', 'input_type', 'user_id']), + agents: osqueryAction.agents.length, }); } - osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { - ...omit(osqueryAction, ['type', 'input_type', 'user_id']), - agents: osqueryAction.agents.length, - }); - return { response: osqueryAction, + fleetActionsCount: fleetActions.length, }; }; diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts index 3b88e710fc52b..4956dfd1245bc 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createDynamicQueries, createQueries } from './create_queries'; +import { createDynamicQueries, PARAMETER_NOT_FOUND } from './create_queries'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; @@ -18,6 +18,8 @@ describe('create queries', () => { removed: false, snapshot: true, }; + const TEST_AGENT = 'test-agent'; + const mockedQueriesParams = { queries: [ { @@ -49,69 +51,74 @@ describe('create queries', () => { // Info: getting queries by index (eg. [1], [0]) because can't compare whole query object due to unique action_id generated. describe('dynamic', () => { const pid = 123; - it('if queries length it should return replaced list of queries', async () => { - const queries = await createDynamicQueries( - mockedQueriesParams, - { + it('alertData, multi queries, should replace queries and show errors', async () => { + const queries = await createDynamicQueries({ + params: mockedQueriesParams, + agents: [TEST_AGENT], + alertData: { process: { pid, }, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + expect(queries[0].error).toBe(undefined); + expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); expect(queries[1].error).toBe( "This query hasn't been called due to parameter used and its value not found in the alert." ); - expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); expect(queries[2].query).toBe('SELECT * FROM processes;'); + expect(queries[2].error).toBe(undefined); }); - it('if single query it should return one replaced query ', async () => { - const queries = await createDynamicQueries( - mockedSingleQueryParams, - { - process: { - pid, - }, + + it('alertData, single query, existing param should return changed query', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + alertData: { + process: { pid }, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + expect(queries[0].error).toBe(undefined); }); - it('if single query with not existing parameter it should return query as it is', async () => { - const queries = await createDynamicQueries( - mockedSingleQueryParams, - { + it('alertData, single query, not existing param should return error', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + alertData: { process: {}, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); - expect(queries[0].error).toBe(undefined); + expect(queries[0].error).toBe(PARAMETER_NOT_FOUND); }); - }); - describe('normal', () => { - const TEST_AGENT = 'test-agent'; - it('if queries length it should return not replaced list of queries with agents', async () => { - const queries = await createQueries( - mockedQueriesParams, - [TEST_AGENT], - {} as OsqueryAppContext - ); + it('no alert data, multi query, return unchanged queries no error', async () => { + const queries = await createDynamicQueries({ + params: mockedQueriesParams, + agents: [TEST_AGENT], + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); expect(queries[0].agents).toContain(TEST_AGENT); + expect(queries[0].error).toBe(undefined); expect(queries[2].query).toBe('SELECT * FROM processes;'); expect(queries[2].agents).toContain(TEST_AGENT); + expect(queries[2].error).toBe(undefined); }); - it('if single query should return not replaced query with agents', async () => { - const queries = await createQueries( - mockedSingleQueryParams, - [TEST_AGENT], - {} as OsqueryAppContext - ); + it('no alert data, single query, return unchanged query and no error', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); expect(queries[0].agents).toContain(TEST_AGENT); + expect(queries[0].error).toBe(undefined); }); }); }); diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts index 6551b8252e16b..f7d3601722189 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts @@ -15,47 +15,26 @@ import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/r import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { isSavedQueryPrebuilt } from '../../routes/saved_query/utils'; -export const createQueries = async ( - params: CreateLiveQueryRequestBodySchema, - agents: string[], - osqueryContext: OsqueryAppContext -) => - params.queries?.length - ? map(params.queries, (query) => - pickBy( - { - ...query, - action_id: uuidv4(), - agents, - }, - (value) => !isEmpty(value) || value === true - ) - ) - : [ - pickBy( - { - action_id: uuidv4(), - id: uuidv4(), - query: params.query, - saved_query_id: params.saved_query_id, - saved_query_prebuilt: params.saved_query_id - ? await isSavedQueryPrebuilt( - osqueryContext.service.getPackageService()?.asInternalUser, - params.saved_query_id - ) - : undefined, - ecs_mapping: params.ecs_mapping, - agents, - }, - (value) => !isEmpty(value) - ), - ]; +export const PARAMETER_NOT_FOUND = i18n.translate( + 'xpack.osquery.liveQueryActions.error.notFoundParameters', + { + defaultMessage: + "This query hasn't been called due to parameter used and its value not found in the alert.", + } +); -export const createDynamicQueries = async ( - params: CreateLiveQueryRequestBodySchema, - alertData: ParsedTechnicalFields, - osqueryContext: OsqueryAppContext -) => +interface CreateDynamicQueriesParams { + params: CreateLiveQueryRequestBodySchema; + alertData?: ParsedTechnicalFields; + agents: string[]; + osqueryContext: OsqueryAppContext; +} +export const createDynamicQueries = async ({ + params, + alertData, + agents, + osqueryContext, +}: CreateDynamicQueriesParams) => params.queries?.length ? map(params.queries, ({ query, ...restQuery }) => { const replacedQuery = replacedQueries(query, alertData); @@ -66,7 +45,7 @@ export const createDynamicQueries = async ( ...restQuery, action_id: uuidv4(), alert_ids: params.alert_ids, - agents: params.agent_ids, + agents, }, (value) => !isEmpty(value) || value === true ); @@ -77,8 +56,6 @@ export const createDynamicQueries = async ( action_id: uuidv4(), id: uuidv4(), ...replacedQueries(params.query, alertData), - // just for single queries - we need to overwrite the error property - error: undefined, saved_query_id: params.saved_query_id, saved_query_prebuilt: params.saved_query_id ? await isSavedQueryPrebuilt( @@ -88,13 +65,13 @@ export const createDynamicQueries = async ( : undefined, ecs_mapping: params.ecs_mapping, alert_ids: params.alert_ids, - agents: params.agent_ids, + agents, }, (value) => !isEmpty(value) ), ]; -const replacedQueries = ( +export const replacedQueries = ( query: string | undefined, alertData?: ParsedTechnicalFields ): { query: string | undefined; error?: string } => { @@ -105,10 +82,7 @@ const replacedQueries = ( query: result, ...(skipped ? { - error: i18n.translate('xpack.osquery.liveQueryActions.error.notFoundParameters', { - defaultMessage: - "This query hasn't been called due to parameter used and its value not found in the alert.", - }), + error: PARAMETER_NOT_FOUND, } : {}), }; diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 92ebddf7642e7..9d7ad88da88b6 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -11,6 +11,7 @@ import markdown from 'remark-parse-no-trim'; import { some, filter } from 'lodash'; import deepEqual from 'fast-deep-equal'; import type { ECSMappingOrUndefined } from '@kbn/osquery-io-ts-types'; +import { PARAMETER_NOT_FOUND } from '../../handlers/action/create_queries'; import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { createLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; @@ -93,7 +94,7 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp try { const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username; - const { response: osqueryAction } = await createActionHandler( + const { response: osqueryAction, fleetActionsCount } = await createActionHandler( osqueryContext, request.body, { @@ -102,6 +103,11 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp alertData, } ); + if (!fleetActionsCount) { + return response.badRequest({ + body: PARAMETER_NOT_FOUND, + }); + } return response.ok({ body: { data: osqueryAction }, diff --git a/x-pack/plugins/rule_registry/common/assets.ts b/x-pack/plugins/rule_registry/common/assets.ts index a1df09df18a8f..1e8919a3a07e4 100644 --- a/x-pack/plugins/rule_registry/common/assets.ts +++ b/x-pack/plugins/rule_registry/common/assets.ts @@ -5,5 +5,4 @@ * 2.0. */ -export const TECHNICAL_COMPONENT_TEMPLATE_NAME = `technical-mappings`; -export const ECS_COMPONENT_TEMPLATE_NAME = `ecs-mappings`; +export const TECHNICAL_COMPONENT_TEMPLATE_NAME = `.alerts-technical-mappings`; diff --git a/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts b/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts index 8e956ba0004a2..8f30e07a0d9dc 100644 --- a/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts +++ b/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts @@ -5,7 +5,7 @@ * 2.0. */ import { merge } from 'lodash'; -import { mappingFromFieldMap } from '../../mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { ClusterPutComponentTemplateBody } from '../../types'; import { ecsFieldMap } from '../field_maps/ecs_field_map'; import { technicalRuleFieldMap } from '../field_maps/technical_rule_field_map'; diff --git a/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts b/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts index e110be339d0a0..1315d7f0d1b58 100644 --- a/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts +++ b/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { mappingFromFieldMap } from '../../mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { ClusterPutComponentTemplateBody } from '../../types'; import { technicalRuleFieldMap } from '../field_maps/technical_rule_field_map'; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts index 4e2d591bf88bd..3a6dbc4f20982 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts @@ -13,10 +13,12 @@ it('matches snapshot', () => { expect(experimentalRuleFieldMap).toMatchInlineSnapshot(` Object { "kibana.alert.evaluation.threshold": Object { + "required": false, "scaling_factor": 100, "type": "scaled_float", }, "kibana.alert.evaluation.value": Object { + "required": false, "scaling_factor": 100, "type": "scaled_float", }, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts index 92f93015309c0..3859ebe6df9b6 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts @@ -8,8 +8,12 @@ import * as Fields from '../../technical_rule_data_field_names'; export const experimentalRuleFieldMap = { - [Fields.ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 }, - [Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 }, + [Fields.ALERT_EVALUATION_THRESHOLD]: { + type: 'scaled_float', + scaling_factor: 100, + required: false, + }, + [Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100, required: false }, } as const; export type ExperimentalRuleFieldMap = typeof experimentalRuleFieldMap; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index e7d39a71a1d6d..7c2cc7c2a02af 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -43,15 +43,27 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.duration.us": Object { + "array": false, + "required": false, "type": "long", }, "kibana.alert.end": Object { + "array": false, + "required": false, "type": "date", }, "kibana.alert.flapping": Object { + "array": false, + "required": false, + "type": "boolean", + }, + "kibana.alert.flapping_history": Object { + "array": true, + "required": false, "type": "boolean", }, "kibana.alert.instance.id": Object { + "array": false, "required": true, "type": "keyword", }, @@ -81,6 +93,7 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.consumer": Object { + "array": false, "required": true, "type": "keyword", }, @@ -135,10 +148,13 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.parameters": Object { + "array": false, "ignore_above": 4096, + "required": false, "type": "flattened", }, "kibana.alert.rule.producer": Object { + "array": false, "required": true, "type": "keyword", }, @@ -158,6 +174,7 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.rule_type_id": Object { + "array": false, "required": true, "type": "keyword", }, @@ -197,12 +214,17 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.severity": Object { + "array": false, + "required": false, "type": "keyword", }, "kibana.alert.start": Object { + "array": false, + "required": false, "type": "date", }, "kibana.alert.status": Object { + "array": false, "required": true, "type": "keyword", }, @@ -237,10 +259,13 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.time_range": Object { + "array": false, "format": "epoch_millis||strict_date_optional_time", + "required": false, "type": "date_range", }, "kibana.alert.uuid": Object { + "array": false, "required": true, "type": "keyword", }, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 32d1b45e44cad..ef476f468544b 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -5,225 +5,12 @@ * 2.0. */ +import { alertFieldMap, legacyAlertFieldMap } from '@kbn/alerts-as-data-utils'; import { pickWithPatterns } from '../../pick_with_patterns'; -import * as Fields from '../../technical_rule_data_field_names'; -import { ecsFieldMap } from './ecs_field_map'; export const technicalRuleFieldMap = { - ...pickWithPatterns( - ecsFieldMap, - Fields.TIMESTAMP, - Fields.EVENT_KIND, - Fields.EVENT_ACTION, - Fields.TAGS - ), - [Fields.ALERT_RULE_PARAMETERS]: { type: 'flattened', ignore_above: 4096 }, - [Fields.ALERT_RULE_TYPE_ID]: { type: 'keyword', required: true }, - [Fields.ALERT_RULE_CONSUMER]: { type: 'keyword', required: true }, - [Fields.ALERT_RULE_PRODUCER]: { type: 'keyword', required: true }, - [Fields.SPACE_IDS]: { type: 'keyword', array: true, required: true }, - [Fields.ALERT_UUID]: { type: 'keyword', required: true }, - [Fields.ALERT_INSTANCE_ID]: { type: 'keyword', required: true }, - [Fields.ALERT_START]: { type: 'date' }, - [Fields.ALERT_TIME_RANGE]: { - type: 'date_range', - format: 'epoch_millis||strict_date_optional_time', - }, - [Fields.ALERT_END]: { type: 'date' }, - [Fields.ALERT_DURATION]: { type: 'long' }, - [Fields.ALERT_SEVERITY]: { type: 'keyword' }, - [Fields.ALERT_STATUS]: { type: 'keyword', required: true }, - [Fields.ALERT_FLAPPING]: { type: 'boolean' }, - [Fields.VERSION]: { - type: 'version', - array: false, - required: false, - }, - [Fields.ECS_VERSION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RISK_SCORE]: { - type: 'float', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_STATUS]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_USER]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_REASON]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_SYSTEM_STATUS]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_ACTION_GROUP]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_REASON]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_CASE_IDS]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_AUTHOR]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_CATEGORY]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_UUID]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_CREATED_AT]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_RULE_CREATED_BY]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_DESCRIPTION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_ENABLED]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_EXECUTION_UUID]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_FROM]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_INTERVAL]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_LICENSE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_NAME]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_NOTE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_REFERENCES]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_RULE_ID]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_RULE_NAME_OVERRIDE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_TAGS]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_TO]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_TYPE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_UPDATED_AT]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_RULE_UPDATED_BY]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_VERSION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_FIELD]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_SUPPRESSION_VALUE]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_SUPPRESSION_START]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_END]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_DOCS_COUNT]: { - type: 'long', - array: false, - required: false, - }, - [Fields.ALERT_LAST_DETECTED]: { - type: 'date', - array: false, - required: false, - }, + ...pickWithPatterns(alertFieldMap, '*'), + ...pickWithPatterns(legacyAlertFieldMap, '*'), } as const; export type TechnicalRuleFieldMap = typeof technicalRuleFieldMap; diff --git a/x-pack/plugins/rule_registry/common/field_map/index.ts b/x-pack/plugins/rule_registry/common/field_map/index.ts index fac8575b8af48..e64ba5823e673 100644 --- a/x-pack/plugins/rule_registry/common/field_map/index.ts +++ b/x-pack/plugins/rule_registry/common/field_map/index.ts @@ -7,4 +7,3 @@ export * from './merge_field_maps'; export * from './runtime_type_from_fieldmap'; -export * from './types'; diff --git a/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts b/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts index 124de243352ea..701bab82855d4 100644 --- a/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts +++ b/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { FieldMap } from './types'; + +import type { FieldMap } from '@kbn/alerts-as-data-utils'; export function mergeFieldMaps( first: T1, diff --git a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts index 8ee71356ef706..0b724150f0dcc 100644 --- a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts +++ b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts @@ -8,11 +8,11 @@ import { runtimeTypeFromFieldMap } from './runtime_type_from_fieldmap'; describe('runtimeTypeFromFieldMap', () => { const fieldmapRt = runtimeTypeFromFieldMap({ - keywordField: { type: 'keyword' }, - longField: { type: 'long' }, - booleanField: { type: 'boolean' }, + keywordField: { type: 'keyword', required: false }, + longField: { type: 'long', required: false }, + booleanField: { type: 'boolean', required: false }, requiredKeywordField: { type: 'keyword', required: true }, - multiKeywordField: { type: 'keyword', array: true }, + multiKeywordField: { type: 'keyword', array: true, required: false }, } as const); it('accepts both singular and array fields', () => { diff --git a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts index feb59f88abc7b..93e182e53af63 100644 --- a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts +++ b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts @@ -8,7 +8,7 @@ import { Optional } from 'utility-types'; import { mapValues, pickBy } from 'lodash'; import { either } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; -import { FieldMap } from './types'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; const NumberFromString = new t.Type( 'NumberFromString', diff --git a/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts b/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts deleted file mode 100644 index 1b66496bee19b..0000000000000 --- a/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts +++ /dev/null @@ -1,36 +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 { set } from '@kbn/safer-lodash-set'; -import { FieldMap } from './field_map/types'; - -export function mappingFromFieldMap( - fieldMap: FieldMap, - dynamic: 'strict' | boolean -): estypes.MappingTypeMapping { - const mappings = { - dynamic, - properties: {}, - }; - - const fields = Object.keys(fieldMap).map((key) => { - const field = fieldMap[key]; - return { - name: key, - ...field, - }; - }); - - fields.forEach((field) => { - const { name, required, array, ...rest } = field; - - set(mappings.properties, field.name.split('.').join('.properties.'), rest); - }); - - return mappings; -} diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts index b63fb2aae83d0..083c4d08d4253 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts @@ -12,11 +12,9 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { Dataset } from './index_options'; import { IndexInfo } from './index_info'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import { elasticsearchServiceMock, ElasticsearchClientMock } from '@kbn/core/server/mocks'; -import { - ECS_COMPONENT_TEMPLATE_NAME, - TECHNICAL_COMPONENT_TEMPLATE_NAME, -} from '../../common/assets'; +import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../common/assets'; describe('resourceInstaller', () => { let pluginStop$: Subject; @@ -82,15 +80,11 @@ describe('resourceInstaller', () => { it('should install common resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); - const getResourceNameMock = jest - .fn() - .mockReturnValueOnce(TECHNICAL_COMPONENT_TEMPLATE_NAME) - .mockReturnValueOnce(ECS_COMPONENT_TEMPLATE_NAME); const installer = new ResourceInstaller({ logger: loggerMock.create(), isWriteEnabled: true, disabledRegistrationContexts: [], - getResourceName: getResourceNameMock, + getResourceName: jest.fn(), getClusterClient, areFrameworkAlertsEnabled: false, pluginStop$, @@ -102,26 +96,22 @@ describe('resourceInstaller', () => { expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 1, - expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) + expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) ); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 2, - expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) + expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) ); }); - it('should install common resources when framework alerts are enabled', async () => { + it('should install subset of common resources when framework alerts are enabled', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); - const getResourceNameMock = jest - .fn() - .mockReturnValueOnce(TECHNICAL_COMPONENT_TEMPLATE_NAME) - .mockReturnValueOnce(ECS_COMPONENT_TEMPLATE_NAME); const installer = new ResourceInstaller({ logger: loggerMock.create(), isWriteEnabled: true, disabledRegistrationContexts: [], - getResourceName: getResourceNameMock, + getResourceName: jest.fn(), getClusterClient, areFrameworkAlertsEnabled: true, pluginStop$, @@ -131,15 +121,12 @@ describe('resourceInstaller', () => { // ILM policy should be handled by framework expect(mockClusterClient.ilm.putLifecycle).not.toHaveBeenCalled(); - expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + // ECS component template should be handled by framework + expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 1, expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) ); - expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) - ); }); it('should install index level resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index 6af288e57a4a0..59c74b81712d8 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -15,18 +15,16 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { DEFAULT_ALERTS_ILM_POLICY, DEFAULT_ALERTS_ILM_POLICY_NAME, -} from '@kbn/alerting-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME, - TECHNICAL_COMPONENT_TEMPLATE_NAME, -} from '../../common/assets'; +} from '@kbn/alerting-plugin/server'; +import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../common/assets'; import { technicalComponentTemplate } from '../../common/assets/component_templates/technical_component_template'; import { ecsComponentTemplate } from '../../common/assets/component_templates/ecs_component_template'; import type { IndexInfo } from './index_info'; const INSTALLATION_TIMEOUT = 20 * 60 * 1000; // 20 minutes -const TOTAL_FIELDS_LIMIT = 1900; +const TOTAL_FIELDS_LIMIT = 2500; interface ConstructorOptions { getResourceName(relativeName: string): string; getClusterClient: () => Promise; @@ -98,7 +96,7 @@ export class ResourceInstaller { */ public async installCommonResources(): Promise { await this.installWithTimeout('common resources shared between all indices', async () => { - const { getResourceName, logger, areFrameworkAlertsEnabled } = this.options; + const { logger, areFrameworkAlertsEnabled } = this.options; try { // We can install them in parallel @@ -112,16 +110,15 @@ export class ResourceInstaller { name: DEFAULT_ALERTS_ILM_POLICY_NAME, body: DEFAULT_ALERTS_ILM_POLICY, }), + this.createOrUpdateComponentTemplate({ + name: ECS_COMPONENT_TEMPLATE_NAME, + body: ecsComponentTemplate, + }), ]), this.createOrUpdateComponentTemplate({ - name: getResourceName(TECHNICAL_COMPONENT_TEMPLATE_NAME), + name: TECHNICAL_COMPONENT_TEMPLATE_NAME, body: technicalComponentTemplate, }), - - this.createOrUpdateComponentTemplate({ - name: getResourceName(ECS_COMPONENT_TEMPLATE_NAME), - body: ecsComponentTemplate, - }), ]); } catch (err) { logger.error( @@ -315,7 +312,7 @@ export class ResourceInstaller { } private async installNamespacedIndexTemplate(indexInfo: IndexInfo, namespace: string) { - const { logger, getResourceName } = this.options; + const { logger } = this.options; const { componentTemplateRefs, componentTemplates, @@ -329,8 +326,7 @@ export class ResourceInstaller { logger.debug(`Installing index template for ${primaryNamespacedAlias}`); - const technicalComponentNames = [getResourceName(TECHNICAL_COMPONENT_TEMPLATE_NAME)]; - const referencedComponentNames = componentTemplateRefs.map((ref) => getResourceName(ref)); + const technicalComponentNames = [TECHNICAL_COMPONENT_TEMPLATE_NAME]; const ownComponentNames = componentTemplates.map((template) => indexInfo.getComponentTemplateName(template.name) ); @@ -365,11 +361,7 @@ export class ResourceInstaller { // - then we include own component templates registered with this index // - finally, we include technical component templates to make sure the index gets all the // mappings and settings required by all Kibana plugins using rule registry to work properly - composed_of: [ - ...referencedComponentNames, - ...ownComponentNames, - ...technicalComponentNames, - ], + composed_of: [...componentTemplateRefs, ...ownComponentNames, ...technicalComponentNames], template: { settings: { 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 f0eef646a1f28..fdb73c85cecb1 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 @@ -314,7 +314,9 @@ export const createLifecycleExecutor = [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', [EVENT_KIND]: 'signal', [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', - [TAGS]: options.rule.tags, + [TAGS]: Array.from( + new Set([...(currentAlertData?.tags ?? []), ...(options.rule.tags ?? [])]) + ), [VERSION]: ruleDataClient.kibanaVersion, [ALERT_FLAPPING]: flapping, ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), diff --git a/x-pack/plugins/rule_registry/tsconfig.json b/x-pack/plugins/rule_registry/tsconfig.json index a3a2a6d373b2b..1bb9b96e6aa92 100644 --- a/x-pack/plugins/rule_registry/tsconfig.json +++ b/x-pack/plugins/rule_registry/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/data-plugin", "@kbn/alerting-plugin", "@kbn/security-plugin", - "@kbn/safer-lodash-set", "@kbn/rule-data-utils", "@kbn/es-query", "@kbn/data-views-plugin", @@ -32,6 +31,7 @@ "@kbn/logging", "@kbn/securitysolution-io-ts-utils", "@kbn/share-plugin", + "@kbn/alerts-as-data-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 79709d6cc5573..49d0d83a5e8c6 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index cd7cf4d96e698..3f01f0ca7c8bf 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; -exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; +exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 060229539b9ad..b8ec6b35fe6b2 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; -exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; +exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; 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/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 0a1f482c8583f..b47b4004f9a5b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -203,6 +203,11 @@ export interface EndpointAction extends ActionRequestFields { // wait to send back an action result before it will timeout timeout?: number; data: EndpointActionData; + // signature of the endpoint action + signed?: { + data: string; + signature: string; + }; } export interface EndpointActionResponse { diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 5863d525ca0f8..1b6d0bb022f9b 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -90,6 +90,10 @@ export const allowedExperimentalValues = Object.freeze({ * Enables top charts on Alerts Page */ alertsPageChartsEnabled: true, + /** + * Enables the new security flyout over the current alert details flyout + */ + securityFlyoutEnabled: false, /** * Keep DEPRECATED experimental flags that are documented to prevent failed upgrades. diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts index 888b492d28b1d..e85885bb1ab0d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts @@ -14,7 +14,7 @@ import { cleanKibana, deleteCases } from '../../tasks/common'; import { addServiceNowConnector, openAddNewConnectorOption, - selectLastConnectorCreated, + verifyNewConnectorSelected, } from '../../tasks/configure_cases'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -58,7 +58,7 @@ describe('Cases connectors', () => { }); cy.intercept('POST', '/api/actions/connector').as('createConnector'); - cy.intercept('POST', '/api/cases/configure', (req) => { + cy.intercept('PATCH', '/api/cases/configure/*', (req) => { const connector = req.body.connector; req.reply((res) => { res.send(200, { ...configureResult, connector }); @@ -94,14 +94,15 @@ describe('Cases connectors', () => { cy.wait('@createConnector').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); + + verifyNewConnectorSelected(snConnector); + cy.get(TOASTER).should('have.text', "Created 'New connector'"); + cy.get(TOASTER).should('have.text', 'Saved external connection settings'); cy.get(TOASTER).should('not.exist'); - selectLastConnectorCreated(response?.body.id); - - cy.wait('@saveConnector', { timeout: 10000 }).its('response.statusCode').should('eql', 200); + cy.wait('@saveConnector').its('response.statusCode').should('eql', 200); cy.get(SERVICE_NOW_MAPPING).first().should('have.text', 'short_description'); - cy.get(TOASTER).should('have.text', 'Saved external connection settings'); }); }); }); 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/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/cypress/tasks/configure_cases.ts b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts index 4019468ddc43e..0b2f722204f5f 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts @@ -39,6 +39,10 @@ export const openAddNewConnectorOption = () => { }); }; +export const verifyNewConnectorSelected = (connector: Connector) => { + cy.get(CONNECTORS_DROPDOWN).should('have.text', connector.connectorName); +}; + export const selectLastConnectorCreated = (id: string) => { cy.get(CONNECTORS_DROPDOWN).click({ force: true }); cy.get(CONNECTOR(id)).click(); 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/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index f194ef0e463eb..bda2e4d8d3629 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -11,6 +11,7 @@ import { EuiThemeProvider, useEuiTheme } from '@elastic/eui'; import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; +import { ExpandableFlyout, ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; import { useSecuritySolutionNavigation } from '../../../common/components/navigation/use_security_solution_navigation'; import { TimelineId } from '../../../../common/types/timeline'; import { getTimelineShowStatusByIdSelector } from '../../../timelines/components/flyout/selectors'; @@ -80,34 +81,35 @@ export const SecuritySolutionTemplateWrapper: React.FC - - - + - {children} - - - {isTimelineBottomBarVisible && ( - - - - - - )} - + + + {children} + + {isTimelineBottomBarVisible && ( + + + + + + )} + {}} /> + + ); }); diff --git a/x-pack/plugins/rule_registry/common/field_map/types.ts b/x-pack/plugins/security_solution/public/cloud_defend/index.ts similarity index 55% rename from x-pack/plugins/rule_registry/common/field_map/types.ts rename to x-pack/plugins/security_solution/public/cloud_defend/index.ts index 52ee246375ad0..4ec2329d36bd5 100644 --- a/x-pack/plugins/rule_registry/common/field_map/types.ts +++ b/x-pack/plugins/security_solution/public/cloud_defend/index.ts @@ -5,13 +5,13 @@ * 2.0. */ -export interface FieldMap { - [key: string]: { - type: string; - required?: boolean; - array?: boolean; - path?: string; - scaling_factor?: number; - dynamic?: 'strict' | boolean; - }; +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/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index c41eaba862c9c..a7b3395c12c5a 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -8,6 +8,7 @@ import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import type { SetEventsDeleted, SetEventsLoading, @@ -19,6 +20,7 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline'; import { dataTableActions } from '../../../store/data_table'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; type Props = EuiDataGridCellValueElementProps & { columnHeaders: ColumnHeaderOptions[]; @@ -64,7 +66,10 @@ const RowActionComponent = ({ }: Props) => { const { data: timelineNonEcsData, ecs: ecsData, _id: eventId, _index: indexName } = data ?? {}; + const { openFlyout } = useExpandableFlyoutContext(); + const dispatch = useDispatch(); + const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); const columnValues = useMemo( () => @@ -90,14 +95,18 @@ const RowActionComponent = ({ }, }; - dispatch( - dataTableActions.toggleDetailPanel({ - ...updatedExpandedDetail, - tabType, - id: tableId, - }) - ); - }, [dispatch, eventId, indexName, tabType, tableId]); + if (isSecurityFlyoutEnabled) { + openFlyout({}); + } else { + dispatch( + dataTableActions.toggleDetailPanel({ + ...updatedExpandedDetail, + tabType, + id: tableId, + }) + ); + } + }, [dispatch, eventId, indexName, isSecurityFlyoutEnabled, openFlyout, tabType, tableId]); const Action = controlColumn.rowCellRender; diff --git a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx index 12f51c78fedab..0a441904a1f6b 100644 --- a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.tsx @@ -31,7 +31,7 @@ const ErrorToastDispatcherComponent: React.FC = ({ toastLifeTimeMs = 5 toast: { color: 'danger', id, - iconType: 'alert', + iconType: 'error', title, errors: message, toastLifeTimeMs, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx index 99c842cd48810..43807363dda1f 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx @@ -6,7 +6,7 @@ */ import React, { useMemo, useCallback, useEffect, useState } from 'react'; -import { EuiBetaBadge, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import { isActiveTimeline } from '../../../../helpers'; @@ -25,7 +25,6 @@ import { PROCESS_ANCESTRY_ERROR, PROCESS_ANCESTRY_FILTER, } from './translations'; -import { BETA } from '../../../translations'; interface Props { data: TimelineEventsDetailsItem; @@ -102,8 +101,6 @@ export const RelatedAlertsByProcessAncestry = React.memo( ); }, [showContent, cache.alertIds, data, index, originalDocumentId, eventId, scopeId]); - const betaBadge = useMemo(() => , []); - return ( ( } renderContent={renderContent} onToggle={onToggle} - extraAction={betaBadge} /> ); } diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx index 3da0aefb35895..db78f6f2e9257 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx @@ -16,6 +16,7 @@ const ruleName = 'Rule name'; const ruleDesc = 'Rule description'; const testProps = { + isLoading: false, groupBucket: { key: [ruleName, ruleDesc], key_as_string: `${ruleName}|${ruleDesc}`, diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx index 446b872f3d5f1..548aa81272cd9 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx @@ -31,6 +31,7 @@ interface GroupPanelProps { forceState?: 'open' | 'closed'; groupBucket: RawBucket; groupPanelRenderer?: JSX.Element; + isLoading: boolean; level?: number; onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void; renderChildComponent: (groupFilter: Filter[]) => React.ReactNode; @@ -56,6 +57,7 @@ const GroupPanelComponent = ({ forceState, groupBucket, groupPanelRenderer, + isLoading, level = 0, onToggleGroup, renderChildComponent, @@ -89,6 +91,7 @@ const GroupPanelComponent = ({ data-test-subj="grouping-accordion" extraAction={extraAction} forceState={forceState} + isLoading={isLoading} id={`group${level}-${groupFieldValue}`} onToggle={onToggle} paddingSize="m" diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx index c74218cf09e80..d9aa2f589c343 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx @@ -97,6 +97,7 @@ const testProps = { value: 2, }, }, + isLoading: false, pagination: { pageIndex: 0, pageSize: 25, diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx index e0ddd959d8b8d..9956ba8e40c06 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTablePagination } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiProgress, + EuiSpacer, + EuiTablePagination, +} from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import React, { useMemo, useState } from 'react'; import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers'; @@ -36,6 +42,7 @@ export interface GroupingContainerProps { groupPanelRenderer?: (fieldBucket: RawBucket) => JSX.Element | undefined; groupsSelector?: JSX.Element; inspectButton?: JSX.Element; + isLoading: boolean; pagination: { pageIndex: number; pageSize: number; @@ -55,6 +62,7 @@ const GroupingContainerComponent = ({ groupPanelRenderer, groupsSelector, inspectButton, + isLoading, pagination, renderChildComponent, selectedGroup, @@ -96,6 +104,7 @@ const GroupingContainerComponent = ({ forceState={(trigger[groupKey] && trigger[groupKey].state) ?? 'closed'} groupBucket={groupBucket} groupPanelRenderer={groupPanelRenderer && groupPanelRenderer(groupBucket)} + isLoading={isLoading} onToggleGroup={(isOpen) => { setTrigger({ // ...trigger, -> this change will keep only one group at a time expanded and one table displayed @@ -121,6 +130,7 @@ const GroupingContainerComponent = ({ customMetricStats, data.stackByMultipleFields0?.buckets, groupPanelRenderer, + isLoading, renderChildComponent, selectedGroup, takeActionItems, @@ -134,6 +144,7 @@ const GroupingContainerComponent = ({ return ( <> ) : ( - + <> + {isLoading && ( + + )} + + )} diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx index 389645bf549da..81382406d2af4 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx @@ -9,18 +9,13 @@ import type { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import type { FieldSpec } from '@kbn/data-views-plugin/common'; import { CustomFieldPanel } from './custom_field_panel'; -import { GROUP_BY, TECHNICAL_PREVIEW } from '../translations'; +import * as i18n from '../translations'; import { StyledContextMenu, StyledEuiButtonEmpty } from '../styles'; -const none = i18n.translate('xpack.securitySolution.groupsSelector.noneGroupByOptionName', { - defaultMessage: 'None', -}); - interface GroupSelectorProps { fields: FieldSpec[]; groupSelected: string; @@ -34,17 +29,38 @@ const GroupsSelectorComponent = ({ groupSelected = 'none', onGroupChange, options, - title = '', + title = i18n.GROUP_BY, }: GroupSelectorProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const panels: EuiContextMenuPanelDescriptor[] = useMemo( () => [ { id: 'firstPanel', + title: ( + + + {i18n.SELECT_FIELD.toUpperCase()} + + + + + + ), items: [ { 'data-test-subj': 'panel-none', - name: none, + name: i18n.NONE, icon: groupSelected === 'none' ? 'check' : 'empty', onClick: () => onGroupChange('none'), }, @@ -56,9 +72,7 @@ const GroupsSelectorComponent = ({ })), { 'data-test-subj': `panel-custom`, - name: i18n.translate('xpack.securitySolution.groupsSelector.customGroupByOptionName', { - defaultMessage: 'Custom field', - }), + name: i18n.CUSTOM_FIELD, icon: 'empty', panel: 'customPanel', }, @@ -66,9 +80,7 @@ const GroupsSelectorComponent = ({ }, { id: 'customPanel', - title: i18n.translate('xpack.securitySolution.groupsSelector.customGroupByPanelTitle', { - defaultMessage: 'Group By Custom Field', - }), + title: i18n.GROUP_BY_CUSTOM_FIELD, width: 685, content: ( 0 ? selectedOption[0].label : none + groupSelected !== 'none' && selectedOption.length > 0 + ? selectedOption[0].label + : i18n.NONE } size="xs" > - {`${title ?? GROUP_BY}: ${ - groupSelected !== 'none' && selectedOption.length > 0 ? selectedOption[0].label : none + {`${title}: ${ + groupSelected !== 'none' && selectedOption.length > 0 + ? selectedOption[0].label + : i18n.NONE }`} ), @@ -114,25 +130,18 @@ const GroupsSelectorComponent = ({ ); return ( - - - - - - - - - - + + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts b/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts index 2e73e95b80de5..a9cd9a7d233dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts @@ -20,13 +20,40 @@ export const TAKE_ACTION = i18n.translate( } ); -export const TECHNICAL_PREVIEW = i18n.translate( - 'xpack.securitySolution.grouping.technicalPreviewLabel', +export const BETA = i18n.translate('xpack.securitySolution.grouping.betaLabel', { + defaultMessage: 'Beta', +}); + +export const BETA_TOOL_TIP = i18n.translate('xpack.securitySolution.grouping.betaToolTip', { + defaultMessage: + 'Grouping may show only a subset of alerts while in beta. To see all alerts, use the list view by selecting "None"', +}); + +export const GROUP_BY = i18n.translate('xpack.securitySolution.selector.grouping.label', { + defaultMessage: 'Group alerts by', +}); + +export const GROUP_BY_CUSTOM_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.customGroupByPanelTitle', { - defaultMessage: 'Technical Preview', + defaultMessage: 'Group By Custom Field', } ); -export const GROUP_BY = i18n.translate('xpack.securitySolution.selector.grouping.label', { - defaultMessage: 'Group by field', +export const SELECT_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.groupByPanelTitle', + { + defaultMessage: 'Select Field', + } +); + +export const NONE = i18n.translate('xpack.securitySolution.groupsSelector.noneGroupByOptionName', { + defaultMessage: 'None', }); + +export const CUSTOM_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.customGroupByOptionName', + { + defaultMessage: 'Custom field', + } +); 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/components/toasters/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx index 17a2ebe192a44..15178cc72b85a 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx @@ -299,7 +299,7 @@ describe('Toaster', () => { toast: { color: 'danger', errors: ['message'], - iconType: 'alert', + iconType: 'error', id: '9e1f72a9-7c73-4b7f-a562-09940f7daf4a', title: 'Title', }, diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.test.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.test.ts index 34871b2e68efa..8c660a5d0684a 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.test.ts @@ -28,7 +28,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['some error 1', 'some error 2'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, @@ -45,7 +45,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['some error 1'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, @@ -64,7 +64,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['something bad happened'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, @@ -82,7 +82,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['Internal Server Error'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, @@ -99,7 +99,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['some error 1'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, @@ -116,7 +116,7 @@ describe('error_to_toaster', () => { toast: { color: 'danger', errors: ['Network Error'], - iconType: 'alert', + iconType: 'error', id: 'some-made-up-id', title: 'some title', }, diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts index 5825f9ad18f06..0114cb2cf4a45 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts @@ -30,7 +30,7 @@ export const displayErrorToast = ( id, title: errorTitle, color: 'danger', - iconType: 'alert', + iconType: 'error', errors: errorMessages, }; dispatchToaster({ @@ -87,7 +87,7 @@ export const errorToToaster = ({ title, error, color = 'danger', - iconType = 'alert', + iconType = 'error', dispatchToaster, }: ErrorToToasterArgs) => { let toast: AppToast; diff --git a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx index aac4305f8518c..0e29b3edba7ab 100644 --- a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx @@ -8,6 +8,7 @@ import type { FieldSpec } from '@kbn/data-views-plugin/common'; import React, { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { GROUP_BY } from '../../../components/grouping/translations'; import type { TableId } from '../../../../../common/types'; import { getDefaultGroupingOptions } from '../../../../detections/components/alerts_table/grouping_settings'; import type { State } from '../../../store'; @@ -101,7 +102,7 @@ export const useGetGroupingSelector = ({ }} fields={fields} options={options} - title={'Group Alerts Selector'} + title={GROUP_BY} /> ), [ diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 5761e98dbe366..d0901071063ab 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -20,6 +20,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; +import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; import { ConsoleManager } from '../../management/components/console'; import type { State } from '../store'; import { createStore } from '../store'; @@ -66,13 +67,15 @@ export const TestProvidersComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> - - Promise.resolve(cellActions)} - > - {children} - - + + + Promise.resolve(cellActions)} + > + {children} + + + diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx index 2e4576576dc60..6c5f68d546f97 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx @@ -14,7 +14,6 @@ import { v4 as uuidv4 } from 'uuid'; import type { Filter } from '@kbn/es-query'; import { buildEsQuery } from '@kbn/es-query'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; -import type { ReactNode } from 'react-markdown'; import { useGetGroupingSelector } 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'; @@ -68,10 +67,10 @@ interface OwnProps { runtimeMappings: MappingRuntimeFields; signalIndexName: string | null; currentAlertStatusFilterValue?: Status; - renderChildComponent: (groupingFilters: Filter[]) => ReactNode; + renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement; } -type AlertsTableComponentProps = OwnProps & PropsFromRedux; +export type AlertsTableComponentProps = OwnProps & PropsFromRedux; export const GroupedAlertsTableComponent: React.FC = ({ defaultFilters = [], @@ -91,10 +90,10 @@ export const GroupedAlertsTableComponent: React.FC = const dispatch = useDispatch(); const groupingId = tableId; - const getGroupbyIdSelector = groupSelectors.getGroupByIdSelector(); + const getGroupByIdSelector = groupSelectors.getGroupByIdSelector(); const { activeGroup: selectedGroup } = - useSelector((state: State) => getGroupbyIdSelector(state, groupingId)) ?? defaultGroup; + useSelector((state: State) => getGroupByIdSelector(state, groupingId)) ?? defaultGroup; const { browserFields, @@ -237,41 +236,50 @@ export const GroupedAlertsTableComponent: React.FC = [defaultFilters, getGlobalQuery, takeActionItems] ); - if (loading || isLoadingGroups || isEmpty(selectedPatterns)) { + const groupedAlerts = useMemo( + () => + isNoneGroup(selectedGroup) ? ( + renderChildComponent([]) + ) : ( + + getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket) + } + customMetricStats={(fieldBucket: RawBucket) => + getSelectedGroupCustomMetrics(selectedGroup, fieldBucket) + } + data={alertsGroupsData?.aggregations ?? {}} + groupPanelRenderer={(fieldBucket: RawBucket) => + getSelectedGroupButtonContent(selectedGroup, fieldBucket) + } + groupsSelector={groupsSelector} + inspectButton={inspect} + isLoading={loading || isLoadingGroups} + pagination={pagination} + renderChildComponent={renderChildComponent} + selectedGroup={selectedGroup} + takeActionItems={getTakeActionItems} + unit={defaultUnit} + /> + ), + [ + alertsGroupsData?.aggregations, + getTakeActionItems, + groupsSelector, + inspect, + isLoadingGroups, + loading, + pagination, + renderChildComponent, + selectedGroup, + ] + ); + + if (isEmpty(selectedPatterns)) { return null; } - const dataTable = renderChildComponent([]); - - return ( - <> - {isNoneGroup(selectedGroup) ? ( - dataTable - ) : ( - <> - - getSelectedGroupButtonContent(selectedGroup, fieldBucket) - } - badgeMetricStats={(fieldBucket: RawBucket) => - getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket) - } - customMetricStats={(fieldBucket: RawBucket) => - getSelectedGroupCustomMetrics(selectedGroup, fieldBucket) - } - /> - - )} - - ); + return groupedAlerts; }; const makeMapStateToProps = () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index f2d1c860bf390..8cb24295f62a4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; import { waitFor, render, fireEvent } from '@testing-library/react'; -import type { Filter, Query } from '@kbn/es-query'; +import type { Filter } from '@kbn/es-query'; import useResizeObserver from 'use-resize-observer/polyfilled'; import '../../../common/mock/match_media'; @@ -19,6 +18,7 @@ import { SUB_PLUGINS_REDUCER, TestProviders, } from '../../../common/mock'; +import type { AlertsTableComponentProps } from './grouped_alerts'; import { GroupedAlertsTableComponent } from './grouped_alerts'; import { TableId } from '../../../../common/types'; import { useSourcererDataView } from '../../../common/containers/sourcerer'; @@ -28,8 +28,8 @@ import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; import type { State } from '../../../common/store'; import { createStore } from '../../../common/store'; -import { AlertsTableComponent } from '.'; import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; +import { defaultGroup } from '../../../common/store/grouping/defaults'; jest.mock('../../../common/containers/sourcerer'); jest.mock('../../../common/containers/use_global_time', () => ({ @@ -149,6 +149,18 @@ const state: State = { const { storage } = createSecuritySolutionStorageMock(); const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); +const groupingStore = createStore( + { + ...state, + groups: { + groupById: { [`${TableId.test}`]: { ...defaultGroup, activeGroup: 'host.name' } }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage +); + jest.mock('./timeline_actions/use_add_bulk_to_timeline', () => ({ useAddBulkToTimelineAction: jest.fn(() => {}), })); @@ -165,52 +177,64 @@ const sourcererDataView = { }, browserFields: {}, }; +const renderChildComponent = (groupingFilters: Filter[]) =>

      ; -const from = '2020-07-07T08:20:18.966Z'; -const to = '2020-07-08T08:20:18.966Z'; -const renderChildComponent = (groupingFilters: Filter[]) => ( - -); +const testProps: AlertsTableComponentProps = { + defaultFilters: [], + dispatch: jest.fn(), + from: '2020-07-07T08:20:18.966Z', + globalFilters: [], + globalQuery: { + query: 'query', + language: 'language', + }, + hasIndexMaintenance: true, + hasIndexWrite: true, + loading: false, + renderChildComponent, + runtimeMappings: {}, + signalIndexName: 'test', + tableId: TableId.test, + to: '2020-07-08T08:20:18.966Z', +}; describe('GroupedAlertsTable', () => { - (useSourcererDataView as jest.Mock).mockReturnValue({ - ...sourcererDataView, - selectedPatterns: ['myFakebeat-*'], + beforeEach(() => { + jest.clearAllMocks(); + (useSourcererDataView as jest.Mock).mockReturnValue({ + ...sourcererDataView, + selectedPatterns: ['myFakebeat-*'], + }); }); - it('renders correctly', () => { - const wrapper = shallow( + it('renders alerts table when no group selected', () => { + const { getByTestId, queryByTestId } = render( - + ); + expect(getByTestId('alerts-table')).toBeInTheDocument(); + expect(queryByTestId('grouping-table')).not.toBeInTheDocument(); + }); - expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); + it('renders grouped alerts when group selected', () => { + const { getByTestId, queryByTestId } = render( + + + + ); + expect(getByTestId('grouping-table')).toBeInTheDocument(); + expect(queryByTestId('alerts-table')).not.toBeInTheDocument(); + expect(queryByTestId('is-loading-grouping-table')).not.toBeInTheDocument(); + }); + + it('renders loading when expected', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('is-loading-grouping-table')).toBeInTheDocument(); }); // Not a valid test as of now.. because, table is used from trigger actions.. diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx index 1b47ec16714f8..044e893572151 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx @@ -38,7 +38,11 @@ export const IsolateHost = React.memo( return caseInfo.id; }); - const { loading, isolateHost } = useHostIsolation({ endpointId, comment, caseIds }); + const { loading, isolateHost } = useHostIsolation({ + endpointId, + comment, + caseIds: caseIds.length > 0 ? caseIds : undefined, + }); const confirmHostIsolation = useCallback(async () => { const hostIsolated = await isolateHost(); 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/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/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/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index d1e236f897ade..0f29e1d46801d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -12,7 +12,7 @@ import type { PluginStartContract as CasesPluginStartContract, } from '@kbn/cases-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import type { FleetStartContract } from '@kbn/fleet-plugin/server'; +import type { FleetStartContract, MessageSigningServiceInterface } from '@kbn/fleet-plugin/server'; import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { @@ -61,6 +61,7 @@ export interface EndpointAppContextServiceStartContract { cases: CasesPluginStartContract | undefined; featureUsageService: FeatureUsageService; experimentalFeatures: ExperimentalFeatures; + messageSigningService: MessageSigningServiceInterface | undefined; } /** @@ -223,4 +224,12 @@ export class EndpointAppContextService { return this.startDependencies.exceptionListsClient; } + + public getMessageSigningService(): MessageSigningServiceInterface { + if (!this.startDependencies?.messageSigningService) { + throw new EndpointAppContentServicesNotStartedError(); + } + + return this.startDependencies.messageSigningService; + } } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index be0738c5f0288..7b57a384e0e10 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -33,6 +33,7 @@ import { createMockAgentPolicyService, createMockAgentService, createMockPackageService, + createMessageSigningServiceMock, } from '@kbn/fleet-plugin/server/mocks'; // A TS error (TS2403) is thrown when attempting to export the mock function below from Cases // plugin server `index.ts`. Its unclear what is actually causing the error. Since this is a Mock @@ -176,6 +177,7 @@ export const createMockEndpointAppContextServiceStartContract = }, featureUsageService: createFeatureUsageServiceMock(), experimentalFeatures: createMockConfig().experimentalFeatures, + messageSigningService: createMessageSigningServiceMock(), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 0c89c183f0835..42e337efba2d9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -108,6 +108,12 @@ describe('Response actions', () => { const routerMock = httpServiceMock.createRouter(); mockResponse = httpServerMock.createResponseFactory(); const startContract = createMockEndpointAppContextServiceStartContract(); + (startContract.messageSigningService?.sign as jest.Mock).mockImplementation(() => { + return { + data: 'thisisthedata', + signature: 'thisisasignature', + }; + }); endpointAppContextService = new EndpointAppContextService(); const mockSavedObjectClient = savedObjectsClientMock.create(); @@ -637,6 +643,35 @@ describe('Response actions', () => { expect(responseBody.action).toBeUndefined(); }); + it('signs the action', async () => { + const ctx = await callRoute( + ISOLATE_HOST_ROUTE_V2, + { + body: { endpoint_ids: ['XYZ'] }, + }, + { endpointDsExists: true } + ); + + const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index; + const actionDocs: [ + { index: string; body?: LogsEndpointAction }, + { index: string; body?: EndpointAction } + ] = [ + indexDoc.mock.calls[0][0] as estypes.IndexRequest, + indexDoc.mock.calls[1][0] as estypes.IndexRequest, + ]; + + expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX); + expect(actionDocs[1].body?.signed).toEqual({ + data: 'thisisthedata', + signature: 'thisisasignature', + }); + + expect(mockResponse.ok).toBeCalled(); + const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse; + expect(responseBody.action).toBeTruthy(); + }); + it('handles errors', async () => { const ErrMessage = 'Uh oh!'; await callRoute( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 559cd0009a4e2..b4197bb947d93 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -335,21 +335,36 @@ function responseActionRequestHandler( { index: AGENT_ACTIONS_INDEX, - body: { - ...doc.EndpointActions, - '@timestamp': doc['@timestamp'], - agents, - timeout: 300, // 5 minutes - user_id: doc.user.id, - }, + body: signedFleetActionDoc, refresh: 'wait_for', }, - { meta: true } + { + meta: true, + } ); if (fleetActionIndexResult.statusCode !== 201) { 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/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index ddcbf2cbdd6e4..c4490e418b859 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -135,6 +135,7 @@ export const importRulesRoute = ( success: actionConnectorSuccess, warnings: actionConnectorWarnings, errors: actionConnectorErrors, + rulesWithMigratedActions, } = await importRuleActionConnectors({ actionConnectors, actionsClient, @@ -142,9 +143,12 @@ export const importRulesRoute = ( rules: migratedParsedObjectsWithoutDuplicateErrors, overwrite: request.query.overwrite_action_connectors, }); + + // rulesWithMigratedActions: Is returened only in case connectors were exorted from different namesapce and the + // original rules actions' ids were replaced with new destinationIds const parsedRules = actionConnectorErrors.length ? [] - : migratedParsedObjectsWithoutDuplicateErrors; + : rulesWithMigratedActions || migratedParsedObjectsWithoutDuplicateErrors; // gather all exception lists that the imported rules reference const foundReferencedExceptionLists = await getReferencedExceptionLists({ @@ -166,7 +170,6 @@ export const importRulesRoute = ( existingLists: foundReferencedExceptionLists, allowMissingConnectorSecrets: !!actionConnectors.length, }); - const errorsResp = importRuleResponse.filter((resp) => isBulkError(resp)) as BulkError[]; const successes = importRuleResponse.filter((resp) => { if (isImportRegular(resp)) { 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 455274006297e..2f81d1284eb53 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 @@ -36,7 +36,7 @@ const actionsClient = actionsClientMock.create(); actionsClient.getAll.mockResolvedValue([]); const core = coreMock.createRequestHandlerContext(); -describe('checkRuleExceptionReferences', () => { +describe('importRuleActionConnectors', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -55,7 +55,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2() as never, + actionsImporter: actionsImporter2(), rules, overwrite: false, }); @@ -82,7 +82,6 @@ describe('checkRuleExceptionReferences', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -92,7 +91,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: false, }); @@ -100,7 +99,6 @@ describe('checkRuleExceptionReferences', () => { expect(res).toEqual({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }); @@ -110,7 +108,6 @@ describe('checkRuleExceptionReferences', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -143,7 +140,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: ruleWith2Connectors, overwrite: false, }); @@ -151,7 +148,6 @@ describe('checkRuleExceptionReferences', () => { expect(res).toEqual({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }); @@ -163,7 +159,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: false, }); @@ -190,7 +186,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: [ { ...getImportRulesSchemaMock(), @@ -235,7 +231,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: [ { ...getImportRulesSchemaMock(), @@ -285,18 +281,17 @@ describe('checkRuleExceptionReferences', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 2, - successResults: [], errors: [], warnings: [], }), }); const actionsImporter2 = core.savedObjects.getImporter; - const actionsImporter2Import = actionsImporter2().import; + const actionsImporter2Importer = actionsImporter2(); const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2Import as never, + actionsImporter: actionsImporter2Importer, rules: rulesWithoutActions, overwrite: false, }); @@ -307,7 +302,7 @@ describe('checkRuleExceptionReferences', () => { errors: [], warnings: [], }); - expect(actionsImporter2Import).not.toBeCalled(); + expect(actionsImporter2Importer.import).not.toBeCalled(); }); it('should skip importing the action-connectors if all connectors have been imported/created before', async () => { @@ -322,12 +317,12 @@ describe('checkRuleExceptionReferences', () => { }, ]); const actionsImporter2 = core.savedObjects.getImporter; - const actionsImporter2Import = actionsImporter2().import; + const actionsImporter2Importer = actionsImporter2(); const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2Import as never, + actionsImporter: actionsImporter2Importer, rules, overwrite: false, }); @@ -338,7 +333,7 @@ describe('checkRuleExceptionReferences', () => { errors: [], warnings: [], }); - expect(actionsImporter2Import).not.toBeCalled(); + expect(actionsImporter2Importer.import).not.toBeCalled(); }); it('should not skip importing the action-connectors if all connectors have been imported/created before when overwrite is true', async () => { @@ -346,7 +341,6 @@ describe('checkRuleExceptionReferences', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -367,7 +361,7 @@ describe('checkRuleExceptionReferences', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: true, }); @@ -377,7 +371,194 @@ describe('checkRuleExceptionReferences', () => { successCount: 1, errors: [], warnings: [], - successResults: [], + }); + }); + + it('should import one rule with connector successfully even if it was exported from different namespaces by generating destinationId and replace the old actionId with it', async () => { + const successResults = [ + { + destinationId: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + ]; + core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ + import: jest.fn().mockResolvedValue({ + success: true, + successCount: 1, + successResults, + errors: [], + warnings: [], + }), + }); + const actionsImporter = core.savedObjects.getImporter; + + actionsClient.getAll.mockResolvedValue([]); + + const res = await importRuleActionConnectors({ + actionConnectors, + actionsClient, + actionsImporter: actionsImporter(), + rules, + overwrite: false, + }); + const rulesWithMigratedActions = [ + { + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + risk_score: 55, + rule_id: 'rule-1', + severity: 'high', + type: 'query', + }, + ]; + + expect(res).toEqual({ + success: true, + successCount: 1, + errors: [], + warnings: [], + rulesWithMigratedActions, + }); + }); + + it('should import multiple rules with connectors successfully even if they were exported from different namespaces by generating destinationIds and replace the old actionIds with them', async () => { + const multipleRules = [ + { + ...getImportRulesSchemaMock(), + actions: [ + { + group: 'default', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + action_type_id: '.webhook', + params: {}, + }, + ], + }, + { + ...getImportRulesSchemaMock(), + rule_id: 'rule_2', + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + actions: [ + { + group: 'default', + id: '11abc78e0-9031-11ed-b076-53cc4d57aaw', + action_type_id: '.index', + params: {}, + }, + ], + }, + ]; + const successResults = [ + { + destinationId: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + { + destinationId: '892cab9bb-535f-45dd-b9c2-5bc1bc0db96', + id: '11abc78e0-9031-11ed-b076-53cc4d57aaw', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + ]; + core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ + import: jest.fn().mockResolvedValue({ + success: true, + successCount: 1, + successResults, + errors: [], + warnings: [], + }), + }); + const actionsImporter = core.savedObjects.getImporter; + const actionConnectorsWithIndex = [ + ...actionConnectors, + { + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.webhook', + name: 'webhook', + isMissingSecrets: false, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }, + ]; + actionsClient.getAll.mockResolvedValue([]); + + const res = await importRuleActionConnectors({ + actionConnectors: actionConnectorsWithIndex, + actionsClient, + actionsImporter: actionsImporter(), + rules: multipleRules, + overwrite: false, + }); + const rulesWithMigratedActions = [ + { + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + risk_score: 55, + rule_id: 'rule-1', + severity: 'high', + type: 'query', + }, + { + actions: [ + { + action_type_id: '.index', + group: 'default', + id: '892cab9bb-535f-45dd-b9c2-5bc1bc0db96', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + rule_id: 'rule_2', + query: 'user.name: root or user.name: admin', + risk_score: 55, + severity: 'high', + type: 'query', + }, + ]; + + expect(res).toEqual({ + success: true, + successCount: 1, + errors: [], + warnings: [], + rulesWithMigratedActions, }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts index 3d2fc9fb7c6b6..dda1a28192eef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts @@ -9,6 +9,7 @@ import { Readable } from 'stream'; import type { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; import type { SavedObject } from '@kbn/core-saved-objects-server'; +import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; import type { WarningSchema } from '../../../../../../../common/detection_engine/schemas/response'; import { checkIfActionsHaveMissingConnectors, @@ -17,6 +18,7 @@ import { handleActionsHaveNoConnectors, mapSOErrorToRuleError, returnErroredImportResult, + updateRuleActionsWithMigratedResults, } from './utils'; import type { ImportRuleActionConnectorsParams, ImportRuleActionConnectorsResult } from './types'; @@ -71,12 +73,21 @@ export const importRuleActionConnectors = async ({ overwrite, createNewCopies: false, }); + /* + // When a connector is exported from one namespace and imported to another, it does not result in an error, but instead a new object is created with + // new destination id and id will have the old origin id, so in order to be able to use the newly generated Connectors id, this util is used to swap the old id with the + // new destination Id + */ + let rulesWithMigratedActions: Array | undefined; + if (successResults?.some((res) => res.destinationId)) + rulesWithMigratedActions = updateRuleActionsWithMigratedResults(rules, successResults); + return { success, successCount, - successResults, errors: errors ? mapSOErrorToRuleError(errors) : [], warnings: (warnings as WarningSchema[]) || [], + rulesWithMigratedActions, }; } catch (error) { return returnErroredImportResult(error); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts index 8ba6c41c42dde..3d9471016a859 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts @@ -6,10 +6,7 @@ */ import type { ISavedObjectsImporter, SavedObject } from '@kbn/core-saved-objects-server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { - SavedObjectsImportFailure, - SavedObjectsImportSuccess, -} from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; import type { WarningSchema } from '../../../../../../../common/detection_engine/schemas/response'; import type { BulkError } from '../../../../routes/utils'; @@ -17,9 +14,9 @@ import type { BulkError } from '../../../../routes/utils'; export interface ImportRuleActionConnectorsResult { success: boolean; successCount: number; - successResults?: SavedObjectsImportSuccess[]; errors: BulkError[] | []; warnings: WarningSchema[] | []; + rulesWithMigratedActions?: Array; } export interface ImportRuleActionConnectorsParams { 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 6d9b3b9da9e6d..caea8ad664f01 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 @@ -5,7 +5,10 @@ * 2.0. */ import { pick } from 'lodash'; -import type { SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectsImportFailure, + SavedObjectsImportSuccess, +} from '@kbn/core-saved-objects-common'; import type { SavedObject } from '@kbn/core-saved-objects-server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; import type { BulkError } from '../../../../../routes/utils'; @@ -127,3 +130,43 @@ export const checkIfActionsHaveMissingConnectors = ( } return null; }; + +export const mapActionIdToNewDestinationId = ( + connectorsImportResult: SavedObjectsImportSuccess[] +) => { + return connectorsImportResult.reduce( + (acc: { [actionId: string]: string }, { destinationId, id }) => { + acc[id] = destinationId || id; + return acc; + }, + {} + ); +}; + +export const swapNonDefaultSpaceIdWithDestinationId = ( + rule: RuleToImport, + actionIdDestinationIdLookup: { [actionId: string]: string } +) => { + return rule.actions?.map((action) => { + const destinationId = actionIdDestinationIdLookup[action.id]; + return { ...action, id: destinationId }; + }); +}; +/* +// When a connector is exported from one namespace and imported to another, it does not result in an error, but instead a new object is created with +// new destination id and id will have the old origin id, so in order to be able to use the newly generated Connectors id, this util is used to swap the old id with the +// new destination Id +*/ +export const updateRuleActionsWithMigratedResults = ( + rules: Array, + connectorsImportResult: SavedObjectsImportSuccess[] +): Array => { + const actionIdDestinationIdLookup = mapActionIdToNewDestinationId(connectorsImportResult); + return rules.map((rule) => { + if (rule instanceof Error) return rule; + return { + ...rule, + actions: swapNonDefaultSpaceIdWithDestinationId(rule, actionIdDestinationIdLookup), + }; + }); +}; 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/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index db7a32670727b..cbc595a22bbf0 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -21,10 +21,10 @@ import type { Logger } from '@kbn/core/server'; import { SavedObjectsClient } from '@kbn/core/server'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; -import type { FieldMap } from '@kbn/rule-registry-plugin/common/field_map'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; import { technicalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/technical_rule_field_map'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import type { ListPluginSetup } from '@kbn/lists-plugin/server'; @@ -220,6 +220,7 @@ export class Plugin implements ISecuritySolutionPlugin { Object.entries(aadFieldConversion).forEach(([key, value]) => { aliasesFieldMap[key] = { type: 'alias', + required: false, path: value, }; }); @@ -509,6 +510,7 @@ export class Plugin implements ISecuritySolutionPlugin { registerListsServerExtension: this.lists?.registerExtension, featureUsageService, experimentalFeatures: config.experimentalFeatures, + messageSigningService: plugins.fleet?.messageSigningService, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 6f831e7633dbb..1cc1a355d3537 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", @@ -141,5 +142,7 @@ "@kbn/securitysolution-ecs", "@kbn/cell-actions", "@kbn/shared-ux-router", + "@kbn/alerts-as-data-utils", + "@kbn/expandable-flyout", ] } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts index 9a4026b582381..9afffb7c10ceb 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts @@ -124,6 +124,7 @@ describe('fetchSearchSourceQuery', () => { Object { "range": Object { "time": Object { + "format": "strict_date_optional_time", "gt": "2020-02-09T23:12:41.941Z", }, }, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts index e033f9c6ef4a8..bc22a228ce988 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts @@ -122,7 +122,11 @@ export function updateSearchSource( // add additional filter for documents with a timestamp greater then // the timestamp of the previous run, so that those documents are not counted twice const field = index.fields.find((f) => f.name === timeFieldName); - const addTimeRangeField = buildRangeFilter(field!, { gt: latestTimestamp }, index); + const addTimeRangeField = buildRangeFilter( + field!, + { gt: latestTimestamp, format: 'strict_date_optional_time' }, + index + ); filters.push(addTimeRangeField); } } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx index be34c715fb8d4..fc51ae0ba1ccf 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx @@ -6,6 +6,7 @@ */ import React, { useEffect, useState } from 'react'; +import { isEmpty } from 'lodash'; import { EuiIcon, EuiText, @@ -45,10 +46,25 @@ export const IndexParamsFields = ({ ); const [isActionConnectorChanged, setIsActionConnectorChanged] = useState(false); - const getDocumentToIndex = (doc: Array> | undefined) => - doc && doc.length > 0 ? (doc[0] as unknown as string) : undefined; + const getDocumentToIndex = (docs: Array> | undefined) => { + // 'documents' param is stored as an array of objects but the JSON editor expects a single + // stringified object - const [documentToIndex, setDocumentToIndex] = useState( + // check that param is a non-empty array + return docs && docs.length > 0 + ? // if the array entry is a string, we can pass it directly to the JSON editor + typeof docs[0] === 'string' + ? docs[0] + : // otherwise check that the array entry is non-empty as sometimes we + // use an empty object to trigger validation but we don't want to auto-populate with an empty object + !isEmpty(docs[0]) + ? // if non-empty object, stringify it into format that JSON editor expects + JSON.stringify(docs[0], null, 2) + : null + : undefined; + }; + + const [documentToIndex, setDocumentToIndex] = useState( getDocumentToIndex(documents) ); const [alertHistoryIndexSuffix, setAlertHistoryIndexSuffix] = useState( @@ -58,9 +74,6 @@ export const IndexParamsFields = ({ useEffect(() => { setDocumentToIndex(getDocumentToIndex(documents)); - if (documents === null) { - setDocumentToIndex('{}'); - } }, [documents]); useEffect(() => { @@ -77,10 +90,14 @@ export const IndexParamsFields = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector?.id]); - const onDocumentsChange = (updatedDocuments: string) => { + const onDocumentsChange = (updatedDocuments: string | null) => { try { - const documentsJSON = JSON.parse(updatedDocuments); - editAction('documents', [documentsJSON], index); + if (updatedDocuments != null) { + const documentsJSON = JSON.parse(updatedDocuments); + editAction('documents', [documentsJSON], index); + } else { + editAction('documents', updatedDocuments, index); + } setDocumentToIndex(updatedDocuments); } catch (e) { // set document as empty to turn on the validation for non empty valid JSON object @@ -180,11 +197,7 @@ export const IndexParamsFields = ({ messageVariables={messageVariables} paramsProperty={'documents'} data-test-subj="documentToIndex" - inputTargetValue={ - documentToIndex === null - ? '{}' // need this to trigger validation - : documentToIndex - } + inputTargetValue={documentToIndex} label={documentsFieldLabel} aria-label={i18n.translate('xpack.stackConnectors.components.index.jsonDocAriaLabel', { defaultMessage: 'Code editor', @@ -202,7 +215,7 @@ export const IndexParamsFields = ({ onBlur={() => { if (!documentToIndex) { // set document as empty to turn on the validation for non empty valid JSON object - onDocumentsChange('{}'); + onDocumentsChange(null); } }} /> 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/rules/alert_actions.test.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts index 19fc96ef86cf1..219790bfe991c 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts @@ -8,9 +8,11 @@ import { populateAlertActions } from './alert_actions'; import { ActionConnector } from './types'; import { MONITOR_STATUS } from '../constants/uptime_alerts'; +import { MONITOR_STATUS as SYNTHETICS_MONITOR_STATUS } from '../constants/synthetics_alerts'; import { MonitorStatusTranslations } from '../translations'; +import { SyntheticsMonitorStatusTranslations } from './synthetics/translations'; -describe('Alert Actions factory', () => { +describe('Legacy Alert Actions factory', () => { it('generate expected action for pager duty', async () => { const resp = populateAlertActions({ groupId: MONITOR_STATUS.id, @@ -32,6 +34,7 @@ describe('Alert Actions factory', () => { defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, }, + isLegacy: true, }); expect(resp).toEqual([ { @@ -57,6 +60,66 @@ describe('Alert Actions factory', () => { ]); }); + it('generate expected action for index', async () => { + const resp = populateAlertActions({ + groupId: MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.index', + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: MonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + }, + isLegacy: true, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + statusMessage: + 'Alert for monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} has recovered', + }, + ], + indexOverride: null, + }, + }, + { + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.latestErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + statusMessage: '{{{context.statusMessage}}}', + }, + ], + indexOverride: null, + }, + }, + ]); + }); + it('generate expected action for slack action connector', async () => { const resp = populateAlertActions({ groupId: MONITOR_STATUS.id, @@ -104,3 +167,157 @@ describe('Alert Actions factory', () => { ]); }); }); + +describe('Alert Actions factory', () => { + it('generate expected action for pager duty', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.pagerduty', + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'resolve', + summary: + 'The alert for the monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} is no longer active: {{context.recoveryReason}}.', + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + }, + ]); + }); + + it('generate expected action for index', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.index', + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.latestErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.locationName}}', + statusMessage: '{{{context.status}}}', + recoveryReason: '{{context.recoveryReason}}', + }, + ], + indexOverride: null, + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.lastErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.locationName}}', + statusMessage: '{{{context.status}}}', + }, + ], + indexOverride: null, + }, + }, + ]); + }); + + it('generate expected action for slack action connector', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.pagerduty', + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: + 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} {{{context.statusMessage}}} The latest error message is {{{context.latestErrorMessage}}}', + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'resolve', + summary: + 'The alert for the monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} is no longer active: {{context.recoveryReason}}.', + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + }, + ]); + }); +}); diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.ts index 9c32fbdf8d3cf..3f8cedf715536 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.ts @@ -43,11 +43,13 @@ export function populateAlertActions({ defaultEmail, groupId, translations, + isLegacy = false, }: { groupId: string; defaultActions: ActionConnector[]; defaultEmail?: DefaultEmail; translations: Translations; + isLegacy?: boolean; }) { const actions: RuleAction[] = []; defaultActions.forEach((aId) => { @@ -78,8 +80,8 @@ export function populateAlertActions({ actions.push(recoveredAction); break; case INDEX_ACTION_ID: - action.params = getIndexActionParams(translations); - recoveredAction.params = getIndexActionParams(translations, true); + action.params = getIndexActionParams(translations, false, isLegacy); + recoveredAction.params = getIndexActionParams(translations, true, isLegacy); actions.push(recoveredAction); break; case SERVICE_NOW_ACTION_ID: @@ -119,8 +121,12 @@ export function populateAlertActions({ return actions; } -function getIndexActionParams(translations: Translations, recovery = false): IndexActionParams { - if (recovery) { +function getIndexActionParams( + translations: Translations, + recovery = false, + isLegacy = false +): IndexActionParams { + if (isLegacy && recovery) { return { documents: [ { @@ -134,14 +140,45 @@ function getIndexActionParams(translations: Translations, recovery = false): Ind indexOverride: null, }; } + + if (isLegacy) { + return { + documents: [ + { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + statusMessage: '{{{context.statusMessage}}}', + latestErrorMessage: '{{{context.latestErrorMessage}}}', + observerLocation: '{{context.observerLocation}}', + }, + ], + indexOverride: null, + }; + } + + if (recovery) { + return { + documents: [ + { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + statusMessage: '{{{context.status}}}', + latestErrorMessage: '{{{context.latestErrorMessage}}}', + observerLocation: '{{context.locationName}}', + recoveryReason: '{{context.recoveryReason}}', + }, + ], + indexOverride: null, + }; + } return { documents: [ { monitorName: '{{context.monitorName}}', monitorUrl: '{{{context.monitorUrl}}}', - statusMessage: '{{{context.statusMessage}}}', - latestErrorMessage: '{{{context.latestErrorMessage}}}', - observerLocation: '{{context.observerLocation}}', + statusMessage: '{{{context.status}}}', + latestErrorMessage: '{{{context.lastErrorMessage}}}', + observerLocation: '{{context.locationName}}', }, ], indexOverride: null, diff --git a/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts b/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts index ff69d3a5e6e7f..be097ed8d8268 100644 --- a/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts +++ b/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts @@ -9,48 +9,62 @@ export const uptimeRuleFieldMap = { // common fields 'monitor.id': { type: 'keyword', + required: false, }, 'url.full': { type: 'keyword', + required: false, }, 'observer.geo.name': { type: 'keyword', + required: false, }, // monitor status alert fields 'error.message': { type: 'text', + required: false, }, 'agent.name': { type: 'keyword', + required: false, }, 'monitor.name': { type: 'keyword', + required: false, }, 'monitor.type': { type: 'keyword', + required: false, }, // tls alert fields 'tls.server.x509.issuer.common_name': { type: 'keyword', + required: false, }, 'tls.server.x509.subject.common_name': { type: 'keyword', + required: false, }, 'tls.server.x509.not_after': { type: 'date', + required: false, }, 'tls.server.x509.not_before': { type: 'date', + required: false, }, 'tls.server.hash.sha256': { type: 'keyword', + required: false, }, // anomaly alert fields 'anomaly.start': { type: 'date', + required: false, }, 'anomaly.bucket_span.minutes': { type: 'keyword', + required: false, }, } as const; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/alerting_default.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/alerting_default.journey.ts index 074cfc2c7dfcb..258be0cd93c59 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/alerting_default.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/alerting_default.journey.ts @@ -38,8 +38,6 @@ journey('AlertingDefaults', async ({ page, params }) => { }); step('Click text=Synthetics', async () => { - await page.click('text=Synthetics'); - await page.click('text=Settings'); expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/alerting'); await page.click('.euiComboBox__inputWrap'); await page.click("text=There aren't any options available"); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/global_parameters.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/global_parameters.journey.ts index a258f8d158d02..59b103d047250 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/global_parameters.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/global_parameters.journey.ts @@ -7,6 +7,7 @@ import { journey, step, before, after, expect } from '@elastic/synthetics'; import { recordVideo } from '@kbn/observability-plugin/e2e/record_video'; +import { byTestId } from '@kbn/observability-plugin/e2e/utils'; import { cleanTestParams } from './services/add_monitor'; import { syntheticsAppPageProvider } from '../../page_objects/synthetics/synthetics_app'; @@ -32,7 +33,7 @@ journey(`GlobalParameters`, async ({ page, params }) => { }); step('Click text=Settings', async () => { - await page.click('text=Settings'); + await page.click(byTestId('settings-page-link')); expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/alerting'); }); step('Click text=Global Parameters', async () => { diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/monitor_selector.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/monitor_selector.journey.ts index eec2c9fc7bf6f..758a3823332f2 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/monitor_selector.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/monitor_selector.journey.ts @@ -7,6 +7,7 @@ import { journey, step, expect, before, after } from '@elastic/synthetics'; import { recordVideo } from '@kbn/observability-plugin/e2e/record_video'; +import { byTestId } from '@kbn/observability-plugin/e2e/utils'; import { addTestMonitor, cleanTestMonitors, @@ -50,7 +51,8 @@ journey(`MonitorSelector`, async ({ page, params }) => { }); step('shows recently viewed monitors', async () => { - await page.click('text=' + testMonitor1); + await page.waitForSelector(byTestId('monitorNameTitle')); + expect(await page.locator(byTestId('monitorNameTitle')).textContent()).toBe(testMonitor1); await page.click('[aria-label="Select a different monitor to view its details"]'); await page.click('text=' + testMonitor2); 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/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/monitor_details/monitor_details_page_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx index 7e083fabc314f..b03ca571fff2d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx @@ -15,7 +15,9 @@ export const MonitorDetailsPageTitle = () => { return ( - {monitor?.name} + + {monitor?.name} + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx index 17f973a13d759..25a150eab71f5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx @@ -74,7 +74,9 @@ export const MonitorAlerts = ({ filters: [ { field: 'observer.geo.name', - values: [selectedLocation.label], + // in 8.6.0, observer.geo.name was mapped to the id, + // so we have to pass both values to maintain history + values: [selectedLocation.label, selectedLocation.id], }, ], }, @@ -117,7 +119,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['active'] }, { field: 'observer.geo.name', - values: [selectedLocation.label], + // in 8.6.0, observer.geo.name was mapped to the id, + // so we have to pass both values to maintain history + values: [selectedLocation.label, selectedLocation.id], }, ], }, @@ -147,7 +151,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['active'] }, { field: 'observer.geo.name', - values: [selectedLocation.label], + // in 8.6.0, observer.geo.name was mapped to the id, + // so we have to pass both values to maintain history + values: [selectedLocation.label, selectedLocation.id], }, ], color: theme.eui.euiColorVis7_behindText, @@ -176,7 +182,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['recovered'] }, { field: 'observer.geo.name', - values: [selectedLocation.label], + // in 8.6.0, observer.geo.name was mapped to the id, + // so we have to pass both values to maintain history + values: [selectedLocation.label, selectedLocation.id], }, ], }, @@ -206,7 +214,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['recovered'] }, { field: 'observer.geo.name', - values: [selectedLocation.label], + // in 8.6.0, observer.geo.name was mapped to the id, + // so we have to pass both values to maintain history + values: [selectedLocation.label, selectedLocation.id], }, ], color: theme.eui.euiColorVis0_behindText, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx index c87aa157eeffa..51e4cce0f2fb0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx @@ -19,7 +19,7 @@ import { EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useFleetPermissions } from '../../../hooks/use_fleet_permissions'; +import { useCanManagePrivateLocation } from '../../../hooks/use_fleet_permissions'; import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; import { PrivateLocation } from '../../../../../../common/runtime_types'; import { FleetPermissionsCallout } from '../../common/components/permissions'; @@ -54,7 +54,7 @@ export const AddLocationFlyout = ({ const { handleSubmit } = form; - const { canReadAgentPolicies } = useFleetPermissions(); + const canManagePrivateLocation = useCanManagePrivateLocation(); const closeFlyout = () => { 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 b6996d4e6149b..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 || []; @@ -129,13 +129,14 @@ export const PrivateLocationsTable = ({ const renderToolRight = () => { return [ 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/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/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 new file mode 100644 index 0000000000000..5337468f6e730 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { 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} + `( + 'returns $expected when `duration` is $duration and `isMilli` $isMilli', + ({ + duration, + expected, + isMilli, + }: { + duration?: number; + expected: string; + isMilli?: boolean; + }) => { + expect(formatTestDuration(duration, isMilli)).toBe(expected); + } + ); +}); 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 fcf07f1cf8714..5d605ad4c2192 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 @@ -17,11 +17,11 @@ export const formatTestDuration = (duration = 0, isMilli = false) => { const secs = isMilli ? duration / 1e3 : duration / 1e6; if (secs >= 60) { - return `${(secs / 60).toFixed(1)} min`; + return `${parseFloat((secs / 60).toFixed(1))} min`; } if (secs >= 1) { - return `${secs.toFixed(1)} s`; + return `${parseFloat(secs.toFixed(1))} s`; } if (isMilli) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx index 8a2671c14cc6c..00d5e8bd7b940 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx @@ -14,8 +14,8 @@ import type { } from '@kbn/fleet-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useEditMonitorLocator } from '../../../apps/synthetics/hooks'; -import { PolicyConfig, MonitorFields } from './types'; -import { ConfigKey, DataStream, TLSFields } from './types'; +import type { PolicyConfig, MonitorFields, TLSFields } from './types'; +import { ConfigKey, DataStream } from './types'; import { SyntheticsPolicyEditExtension } from './synthetics_policy_edit_extension'; import { PolicyConfigContextProvider, @@ -80,10 +80,10 @@ export const SyntheticsPolicyEditExtensionWrapper = memo = { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx index 0c487eb1fdc3d..e80a305afa6fa 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx @@ -52,8 +52,8 @@ export const EditMonitorConfig = ({ monitor, throttling }: Props) => { [ConfigKey.TLS_VERSION]: monitor[ConfigKey.TLS_VERSION], }; - enableTLS = Boolean(monitor[ConfigKey.METADATA].is_tls_enabled); - enableZipUrlTLS = Boolean(monitor[ConfigKey.METADATA].is_zip_url_tls_enabled); + enableTLS = Boolean(monitor[ConfigKey.METADATA]?.is_tls_enabled); + enableZipUrlTLS = Boolean(monitor[ConfigKey.METADATA]?.is_zip_url_tls_enabled); const formattedDefaultConfig: Partial = { [type]: monitor, 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/public/legacy_uptime/state/api/alerts.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts index e0f83c917aa8a..33cbac6468306 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts @@ -87,6 +87,7 @@ export const createAlert = async ({ defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, }, + isLegacy: true, }); const data: NewMonitorStatusAlert = { diff --git a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts index 34caffe5fcdd9..6280ebb3fdd5b 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { updateState } from './common'; +import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; +import { IBasePath } from '@kbn/core/server'; +import { updateState, setRecoveredAlertsContext } from './common'; import { SyntheticsCommonState } from '../../common/runtime_types/alert_rules/common'; +import { StaleDownConfig } from './status_rule/status_rule_executor'; describe('updateState', () => { let spy: jest.SpyInstance; @@ -180,3 +182,153 @@ describe('updateState', () => { `); }); }); + +describe('setRecoveredAlertsContext', () => { + const { alertFactory } = alertsMock.createRuleExecutorServices(); + const { getRecoveredAlerts } = alertFactory.done(); + const alertUuid = 'alert-id'; + const location = 'US Central'; + const configId = '12345'; + const idWithLocation = `${configId}-${location}`; + const basePath = { + publicBaseUrl: 'https://localhost:5601', + } as IBasePath; + const getAlertUuid = () => alertUuid; + + const upConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'up', + location: '', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + }, + }; + + it('sets context correctly when monitor is deleted', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isDeleted: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs: {}, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'Monitor has been deleted', + }); + }); + + it('sets context correctly when location is removed', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isLocationRemoved: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs: {}, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'Location has been removed from the monitor', + }); + }); + + it('sets context correctly when monitor is up', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isLocationRemoved: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + status: 'up', + recoveryReason: 'Monitor has recovered with status Up', + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/alert_rules/common.ts b/x-pack/plugins/synthetics/server/alert_rules/common.ts index 2b629e865a115..e12e85bf82ee2 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/common.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/common.ts @@ -80,12 +80,14 @@ export const setRecoveredAlertsContext = ({ getAlertUuid, spaceId, staleDownConfigs, + upConfigs, }: { alertFactory: RuleExecutorServices['alertFactory']; basePath?: IBasePath; getAlertUuid?: (alertId: string) => string | null; spaceId?: string; staleDownConfigs: AlertOverviewStatus['staleDownConfigs']; + upConfigs: AlertOverviewStatus['upConfigs']; }) => { const { getRecoveredAlerts } = alertFactory.done(); for (const alert of getRecoveredAlerts()) { @@ -95,6 +97,7 @@ export const setRecoveredAlertsContext = ({ const state = alert.getState() as SyntheticsCommonState; let recoveryReason = ''; + let isUp = false; if (state?.idWithLocation && staleDownConfigs[state.idWithLocation]) { const { idWithLocation } = state; @@ -110,8 +113,16 @@ export const setRecoveredAlertsContext = ({ } } + if (state?.idWithLocation && upConfigs[state.idWithLocation]) { + isUp = Boolean(upConfigs[state.idWithLocation]) || false; + recoveryReason = i18n.translate('xpack.synthetics.alerts.monitorStatus.upCheck', { + defaultMessage: `Monitor has recovered with status Up`, + }); + } + alert.setContext({ ...state, + ...(isUp ? { status: 'up' } : {}), ...(recoveryReason ? { [RECOVERY_REASON]: recoveryReason } : {}), ...(basePath && spaceId && alertUuid ? { [ALERT_DETAILS_URL]: getAlertDetailsUrl(basePath, spaceId, alertUuid) } diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts index 7d309c1f595aa..e1719c1e84b93 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts @@ -84,7 +84,7 @@ export const registerSyntheticsStatusCheckRule = ( syntheticsMonitorClient ); - const { downConfigs, staleDownConfigs } = await statusRule.getDownChecks( + const { downConfigs, staleDownConfigs, upConfigs } = await statusRule.getDownChecks( ruleState.meta?.downConfigs as OverviewStatus['downConfigs'] ); @@ -129,6 +129,7 @@ export const registerSyntheticsStatusCheckRule = ( getAlertUuid, spaceId, staleDownConfigs, + upConfigs, }); return { diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts new file mode 100644 index 0000000000000..8a79ae5beab13 --- /dev/null +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts @@ -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 { StatusRuleExecutor } from './status_rule_executor'; +import { loggerMock } from '@kbn/logging-mocks'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; +import { mockEncryptedSO } from '../../synthetics_service/utils/mocks'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; +import { SyntheticsService } from '../../synthetics_service/synthetics_service'; +import moment from 'moment'; +import * as monitorUtils from '../../saved_objects/synthetics_monitor/get_all_monitors'; + +describe('StatusRuleExecutor', () => { + const mockEsClient = elasticsearchClientMock.createElasticsearchClient(); + const logger = loggerMock.create(); + const soClient = savedObjectsClientMock.create(); + + const serverMock: UptimeServerSetup = { + logger, + uptimeEsClient: mockEsClient, + authSavedObjectsClient: soClient, + config: { + service: { + username: 'dev', + password: '12345', + manifestUrl: 'http://localhost:8080/api/manifest', + }, + }, + spaces: { + spacesService: { + getSpaceId: jest.fn().mockReturnValue('test-space'), + }, + }, + encryptedSavedObjects: mockEncryptedSO, + } as unknown as UptimeServerSetup; + + const syntheticsService = new SyntheticsService(serverMock); + + const monitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock); + + it('should only query enabled monitors', async () => { + const spy = jest.spyOn(monitorUtils, 'getAllMonitors').mockResolvedValue([]); + const statusRule = new StatusRuleExecutor( + moment().toDate(), + {}, + soClient, + mockEsClient, + serverMock, + monitorClient + ); + const { downConfigs, staleDownConfigs } = await statusRule.getDownChecks({}); + + expect(downConfigs).toEqual({}); + expect(staleDownConfigs).toEqual({}); + + expect(spy).toHaveBeenCalledWith({ + filter: 'synthetics-monitor.attributes.alert.status.enabled: true', + soClient, + }); + }); + it('marks deleted configs as expected', async () => { + jest.spyOn(monitorUtils, 'getAllMonitors').mockResolvedValue(testMonitors); + const statusRule = new StatusRuleExecutor( + moment().toDate(), + {}, + soClient, + mockEsClient, + serverMock, + monitorClient + ); + + const { downConfigs } = await statusRule.getDownChecks({}); + + expect(downConfigs).toEqual({}); + + const staleDownConfigs = await statusRule.markDeletedConfigs({ + id1: { + location: 'us-east-1', + configId: 'id1', + status: 'down', + timestamp: '2021-06-01T00:00:00.000Z', + monitorQueryId: 'test', + ping: {} as any, + }, + '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { + location: 'US Central DEV', + configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + status: 'down', + timestamp: '2021-06-01T00:00:00.000Z', + monitorQueryId: 'test', + ping: {} as any, + }, + '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_qa': { + location: 'US Central QA', + configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + status: 'down', + timestamp: '2021-06-01T00:00:00.000Z', + monitorQueryId: 'test', + ping: {} as any, + }, + }); + + expect(staleDownConfigs).toEqual({ + id1: { + configId: 'id1', + isDeleted: true, + location: 'us-east-1', + monitorQueryId: 'test', + ping: {}, + status: 'down', + timestamp: '2021-06-01T00:00:00.000Z', + }, + '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { + configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + isLocationRemoved: true, + location: 'US Central DEV', + monitorQueryId: 'test', + ping: {}, + status: 'down', + timestamp: '2021-06-01T00:00:00.000Z', + }, + }); + }); +}); + +const testMonitors = [ + { + type: 'synthetics-monitor', + id: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + attributes: { + type: 'browser', + form_monitor_type: 'multistep', + enabled: true, + alert: { status: { enabled: false } }, + schedule: { unit: 'm', number: '10' }, + 'service.name': '', + config_id: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + tags: [], + timeout: null, + name: 'https://www.google.com', + locations: [ + { + geo: { lon: -95.86, lat: 41.25 }, + isServiceManaged: true, + id: 'us_central_qa', + label: 'US Central QA', + }, + ], + namespace: 'test_monitor', + origin: 'ui', + journey_id: '', + hash: '', + id: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', + project_id: '', + playwright_options: '', + __ui: { + script_source: { is_generated_script: false, file_name: '' }, + is_zip_url_tls_enabled: false, + }, + 'url.port': null, + 'source.zip_url.url': '', + 'source.zip_url.folder': '', + 'source.zip_url.proxy_url': '', + playwright_text_assertion: '', + urls: 'https://www.google.com', + screenshots: 'on', + 'filter_journeys.match': '', + 'filter_journeys.tags': [], + ignore_https_errors: false, + 'throttling.is_enabled': true, + 'throttling.download_speed': '5', + 'throttling.upload_speed': '3', + 'throttling.latency': '20', + 'throttling.config': '5d/3u/20l', + 'ssl.certificate_authorities': '', + 'ssl.certificate': '', + 'ssl.verification_mode': 'full', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + revision: 3, + }, + references: [], + migrationVersion: { 'synthetics-monitor': '8.6.0' }, + coreMigrationVersion: '8.0.0', + updated_at: '2023-02-23T21:19:21.041Z', + created_at: '2023-02-23T21:04:19.579Z', + version: 'WzY1MjUwLDNd', + namespaces: ['test-monitor'], + score: null, + sort: ['https://www.google.com', 1889], + }, +] as any; diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts index 8e3a1948ef9dc..923177dc6b377 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts @@ -10,13 +10,11 @@ import { SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { AlertConfigKey } from '../../../common/constants/monitor_management'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; import { getAllMonitors, processMonitors, } from '../../saved_objects/synthetics_monitor/get_all_monitors'; -import { GetMonitorDownStatusMessageParams } from '../../legacy_uptime/lib/requests/get_monitor_status'; import { queryMonitorStatus } from '../../queries/query_monitor_status'; import { UptimeEsClient } from '../../legacy_uptime/lib/lib'; import { StatusRuleParams } from '../../../common/rules/status_rule'; @@ -26,9 +24,10 @@ import { OverviewStatus, OverviewStatusMetaData, } from '../../../common/runtime_types'; -import { statusCheckTranslations } from '../../legacy_uptime/lib/alerts/translations'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; +import { monitorAttributes } from '../../../common/types/saved_objects'; +import { AlertConfigKey } from '../../../common/constants/monitor_management'; export interface StaleDownConfig extends OverviewStatusMetaData { isDeleted?: boolean; @@ -90,7 +89,7 @@ export class StatusRuleExecutor { await this.getAllLocationNames(); this.monitors = await getAllMonitors({ soClient: this.soClient, - search: `attributes.${AlertConfigKey.STATUS_ENABLED}: true`, + filter: `${monitorAttributes}.${AlertConfigKey.STATUS_ENABLED}: true`, }); const { @@ -204,16 +203,4 @@ export class StatusRuleExecutor { return staleDownConfigs; } - - async getStatusMessage(downMonParams?: GetMonitorDownStatusMessageParams) { - let statusMessage = ''; - if (downMonParams?.info) { - statusMessage = statusCheckTranslations.downMonitorsLabel( - downMonParams.count!, - downMonParams.interval!, - downMonParams.numTimes - ); - } - return statusMessage; - } } diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts index cb12f045a2311..0a766e4a5833d 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts @@ -133,6 +133,17 @@ export const getSyntheticsMonitorSavedObjectType = ( enabled: { type: 'boolean', }, + alert: { + properties: { + status: { + properties: { + enabled: { + type: 'boolean', + }, + }, + }, + }, + }, }, }, management: { diff --git a/x-pack/plugins/synthetics/server/plugin.ts b/x-pack/plugins/synthetics/server/plugin.ts index 598fdd18b229b..c6120c70c3818 100644 --- a/x-pack/plugins/synthetics/server/plugin.ts +++ b/x-pack/plugins/synthetics/server/plugin.ts @@ -13,7 +13,7 @@ import { SavedObjectsClient, SavedObjectsClientContract, } from '@kbn/core/server'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import { SyntheticsMonitorClient } from './synthetics_service/synthetics_monitor/synthetics_monitor_client'; 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/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/task_manager/server/constants.ts b/x-pack/plugins/task_manager/server/constants.ts index e843a0b4815d1..e9294b25e2859 100644 --- a/x-pack/plugins/task_manager/server/constants.ts +++ b/x-pack/plugins/task_manager/server/constants.ts @@ -5,3 +5,13 @@ * 2.0. */ export const TASK_MANAGER_INDEX = '.kibana_task_manager'; +export const CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: string[] = [ + // for testing + 'sampleTaskWithSingleConcurrency', + 'sampleTaskWithLimitedConcurrency', + 'timedTaskWithSingleConcurrency', + 'timedTaskWithLimitedConcurrency', + + // task types requiring a concurrency + 'report:execute', +]; diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index d6d0881cf0d50..374a52ea84b2f 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -22,6 +22,10 @@ import { TaskPoolMock } from './task_pool.mock'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from './mocks'; +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report'], +})); + const executionContext = executionContextServiceMock.createSetupContract(); describe('EphemeralTaskLifecycle', () => { diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index f4616b2f8c205..51cac494d3a67 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -31,6 +31,10 @@ jest.mock('./queries/task_claiming', () => { }; }); +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report', 'quickReport'], +})); + describe('TaskPollingLifecycle', () => { let clock: sinon.SinonFakeTimers; const taskManagerLogger = mockLogger(); diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index d847165bdba9d..6d24e4c0f5987 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -27,6 +27,17 @@ import { taskStoreMock } from '../task_store.mock'; import apm from 'elastic-apm-node'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; +jest.mock('../constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ + 'limitedToZero', + 'limitedToOne', + 'anotherLimitedToZero', + 'anotherLimitedToOne', + 'limitedToTwo', + 'limitedToFive', + ], +})); + const taskManagerLogger = mockLogger(); beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/plugins/task_manager/server/task_scheduling.test.ts b/x-pack/plugins/task_manager/server/task_scheduling.test.ts index 4433a0ad83e8c..7f59f9f362554 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.test.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.test.ts @@ -27,6 +27,10 @@ jest.mock('uuid', () => ({ v4: () => 'v4uuid', })); +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['foo'], +})); + jest.mock('elastic-apm-node', () => ({ currentTraceparent: 'parent', currentTransaction: { diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index cb2f436fa8676..039403816da5e 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -14,6 +14,10 @@ import { TaskTypeDictionary, } from './task_type_dictionary'; +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['foo'], +})); + interface Opts { numTasks: number; } @@ -182,7 +186,6 @@ describe('taskTypeDictionary', () => { definitions.registerTaskDefinitions({ foo: { title: 'foo', - maxConcurrency: 2, createTaskRunner: jest.fn(), }, }); @@ -209,5 +212,19 @@ describe('taskTypeDictionary', () => { `"Task sampleTaskRemovedType has been removed from registration!"` ); }); + + it(`throws error when setting maxConcurrency to a task type that isn't allowed to set it`, () => { + expect(() => { + definitions.registerTaskDefinitions({ + foo2: { + title: 'foo2', + maxConcurrency: 2, + createTaskRunner: jest.fn(), + }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"maxConcurrency setting isn't allowed for task type: foo2"` + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index 9aeafa0a18c7d..3c0e4a0fe5542 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -7,6 +7,7 @@ import { Logger } from '@kbn/core/server'; import { TaskDefinition, taskDefinitionSchema, TaskRunCreatorFunction } from './task'; +import { CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE } from './constants'; /** * Types that are no longer registered and will be marked as unregistered @@ -129,12 +130,25 @@ export class TaskTypeDictionary { throw new Error(`Task ${removed} has been removed from registration!`); } + for (const taskType of Object.keys(taskDefinitions)) { + if ( + taskDefinitions[taskType].maxConcurrency !== undefined && + !CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE.includes(taskType) + ) { + // maxConcurrency is designed to limit how many tasks of the same type a single Kibana + // instance should run at a time. Meaning if you have 8 Kibanas running, you will still + // see up to 8 tasks running at a time but one per Kibana instance. This is helpful for + // reporting purposes but not for many other cases and are better off not setting this value. + throw new Error(`maxConcurrency setting isn't allowed for task type: ${taskType}`); + } + } + try { for (const definition of sanitizeTaskDefinitions(taskDefinitions)) { this.definitions.set(definition.type, definition); } } catch (e) { - this.logger.error('Could not sanitize task definitions'); + this.logger.error(`Could not sanitize task definitions: ${e.message}`); } } } 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 7464ed2fbb4fe..bc8fc835911fb 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9642,15 +9642,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "Impossible de trouver un Lens correspondant.", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "Lens", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "Visualisation ouverte", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "Types", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "Insérez un Lens à partir de modèles existants ou en créant un nouveau modèle. Vous créerez un Lens uniquement pour ce commentaire et ne changerez pas la Bibliothèque Visualize.", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "Sélectionner un Lens", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "Modèle", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "Rechercher…", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "Croissant", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "Meilleure correspondance", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "Trier", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "Décroissant", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "Visualisation", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "Parenthèses gauches attendues", "xpack.cases.markdownEditor.preview": "Aperçu", @@ -10592,8 +10586,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}", @@ -33714,7 +33706,6 @@ "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", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f3c0b84e452b1..4bd272febf95a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9631,15 +9631,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "一致するLensが見つかりません。", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "レンズ", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "ビジュアライゼーションを開く", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "タイプ", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "既存のテンプレートからLensを挿入するか、新しく作成します。このコメントでのみLensが作成されます。Visualize Libraryは変更されません。", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "Lensを選択", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "テンプレート", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "検索…", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "昇順", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "ベストマッチ", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "並べ替え", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "降順", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "ビジュアライゼーション", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "想定される左括弧", "xpack.cases.markdownEditor.preview": "プレビュー", @@ -10579,8 +10573,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}", @@ -33685,7 +33677,6 @@ "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": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e8809adf47caf..d1d3729f26079 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9646,15 +9646,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "未找到匹配的 lens。", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "Lens", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "打开可视化", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "类型", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "通过现有模板或创建新模板来插入 Lens。您将仅为此注释创建 Lens,并且不会更改可视化库。", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "选择 Lens", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "模板", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "搜索……", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "升序", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "最佳匹配", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "排序", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "降序", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "可视化", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "应为左括号", "xpack.cases.markdownEditor.preview": "预览", @@ -10596,8 +10590,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}", @@ -33720,7 +33712,6 @@ "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": "取消", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx index 5349e60a60adb..0cac98f01f636 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx @@ -69,4 +69,18 @@ describe('JsonEditorWithMessageVariables', () => { '{{{myVar}}}' ); }); + + test('renders correct value when the input value prop updates', () => { + const wrapper = mountWithIntl(); + + expect(wrapper.find('[data-test-subj="fooJsonEditor"]').first().prop('value')).toEqual(''); + + const inputTargetValue = '{"new": "value"}'; + wrapper.setProps({ inputTargetValue }); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="fooJsonEditor"]').first().prop('value')).toEqual( + inputTargetValue + ); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index 643e1b69a513d..3485a99c39456 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -37,7 +37,7 @@ interface Props { buttonTitle?: string; messageVariables?: ActionVariable[]; paramsProperty: string; - inputTargetValue?: string; + inputTargetValue?: string | null; label: string; errors?: string[]; areaLabel?: string; @@ -75,6 +75,13 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ const { convertToJson, setXJson, xJson } = useXJsonMode(inputTargetValue ?? null); + useEffect(() => { + if (!xJson && inputTargetValue) { + setXJson(inputTargetValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputTargetValue]); + const onSelectMessageVariable = (variable: ActionVariable) => { const editor = editorRef.current; if (!editor) { 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/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index 193d6abf1433b..6a170363d34a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { Query, TimeRange } from '@kbn/es-query'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; import { AlertsSearchBarProps, QueryLanguageType } from './types'; @@ -32,16 +33,20 @@ export function AlertsSearchBar({ const { value: dataView, loading, error } = useAlertDataView(featureIds); const onQuerySubmit = useCallback( - (payload) => { - const { dateRange, query: nextQuery } = payload; + ({ dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query }) => { onQueryChange({ dateRange, - query: typeof nextQuery?.query === 'string' ? nextQuery.query : '', + query: typeof nextQuery?.query === 'string' ? nextQuery.query : undefined, }); setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType); }, [onQueryChange, setQueryLanguage] ); + const onRefresh = ({ dateRange }: { dateRange: TimeRange }) => { + onQueryChange({ + dateRange, + }); + }; return ( ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts index e75dd56168687..2c94c250c168a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts @@ -17,6 +17,6 @@ export interface AlertsSearchBarProps { query?: string; onQueryChange: (query: { dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' }; - query: string; + query?: string; }) => void; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx index 27188fab622c9..3dc86c5522e8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx @@ -584,7 +584,8 @@ describe('AlertsTable.BulkActions', () => { }); }); - describe('and executing a bulk action', () => { + // FLAKY: https://github.com/elastic/kibana/issues/152176 + describe.skip('and executing a bulk action', () => { it('should return the are all selected flag set to true', async () => { const mockedFn = jest.fn(); const props = { 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/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx index 40204483e430b..271c12aa3f63b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx @@ -156,7 +156,9 @@ describe('Rules list Bulk Edit', () => { queryClient.clear(); }); - describe('bulk actions', () => { + // FLAKY: https://github.com/elastic/kibana/issues/152268 + // FLAKY: https://github.com/elastic/kibana/issues/152267 + describe.skip('bulk actions', () => { beforeEach(async () => { renderWithProviders(); await waitForElementToBeRemoved(() => screen.queryByTestId('centerJustifiedSpinner')); diff --git a/x-pack/plugins/upgrade_assistant/common/constants.ts b/x-pack/plugins/upgrade_assistant/common/constants.ts index 8097ddd7f97d6..4ed5cb9d072c8 100644 --- a/x-pack/plugins/upgrade_assistant/common/constants.ts +++ b/x-pack/plugins/upgrade_assistant/common/constants.ts @@ -7,9 +7,6 @@ export const API_BASE_PATH = '/api/upgrade_assistant'; -// Telemetry constants -export const UPGRADE_ASSISTANT_TELEMETRY = 'upgrade-assistant-telemetry'; - /** * This is the repository where Cloud stores its backup snapshots. */ diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 05964122b9873..922fdc8d89465 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -29,11 +29,7 @@ import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; import { versionService } from './lib/version'; import { createReindexWorker } from './routes/reindex_indices'; import { registerRoutes } from './routes/register_routes'; -import { - telemetrySavedObjectType, - reindexOperationSavedObjectType, - mlSavedObjectType, -} from './saved_object_types'; +import { reindexOperationSavedObjectType, mlSavedObjectType } from './saved_object_types'; import { handleEsError } from './shared_imports'; import { RouteDependencies } from './types'; import type { UpgradeAssistantConfig } from './config'; @@ -88,7 +84,6 @@ export class UpgradeAssistantServerPlugin implements Plugin { this.licensing = licensing; savedObjects.registerType(reindexOperationSavedObjectType); - savedObjects.registerType(telemetrySavedObjectType); savedObjects.registerType(mlSavedObjectType); features.registerElasticsearchFeature({ diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts index e394cac5100f9..fa8c1ac679ad6 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts @@ -6,5 +6,4 @@ */ export { reindexOperationSavedObjectType } from './reindex_operation_saved_object_type'; -export { telemetrySavedObjectType } from './telemetry_saved_object_type'; export { mlSavedObjectType } from './ml_upgrade_operation_saved_object_type'; diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.test.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.test.ts deleted file mode 100644 index e1250ee0ebfe0..0000000000000 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { telemetrySavedObjectMigrations } from './telemetry_saved_object_migrations'; - -describe('Telemetry saved object migration', () => { - describe('7.16.0', () => { - test('removes ui_open and ui_reindex attributes while preserving other attributes', () => { - const doc = { - type: 'upgrade-assistant-telemetry', - id: 'upgrade-assistant-telemetry', - attributes: { - 'test.property': 5, - 'ui_open.cluster': 1, - 'ui_open.indices': 1, - 'ui_open.overview': 1, - 'ui_reindex.close': 1, - 'ui_reindex.open': 1, - 'ui_reindex.start': 1, - 'ui_reindex.stop': 1, - }, - references: [], - updated_at: '2021-09-29T21:17:17.410Z', - migrationVersion: {}, - }; - - expect(telemetrySavedObjectMigrations['7.16.0'](doc)).toStrictEqual({ - type: 'upgrade-assistant-telemetry', - id: 'upgrade-assistant-telemetry', - attributes: { 'test.property': 5 }, - references: [], - updated_at: '2021-09-29T21:17:17.410Z', - migrationVersion: {}, - }); - }); - }); -}); diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.ts deleted file mode 100644 index 7d26065ce0b82..0000000000000 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/migrations/telemetry_saved_object_migrations.ts +++ /dev/null @@ -1,40 +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 { get, omit, flow, some } from 'lodash'; -import type { SavedObjectMigrationFn } from '@kbn/core/server'; - -const v716RemoveUnusedTelemetry: SavedObjectMigrationFn = (doc) => { - // Dynamically defined in 6.7 (https://github.com/elastic/kibana/pull/28878) - // and then statically defined in 7.8 (https://github.com/elastic/kibana/pull/64332). - const attributesBlocklist = [ - 'ui_open.cluster', - 'ui_open.indices', - 'ui_open.overview', - 'ui_reindex.close', - 'ui_reindex.open', - 'ui_reindex.start', - 'ui_reindex.stop', - ]; - - const isDocEligible = some(attributesBlocklist, (attribute: string) => { - return get(doc, 'attributes', attribute); - }); - - if (isDocEligible) { - return { - ...doc, - attributes: omit(doc.attributes, attributesBlocklist), - }; - } - - return doc; -}; - -export const telemetrySavedObjectMigrations = { - '7.16.0': flow(v716RemoveUnusedTelemetry), -}; diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts deleted file mode 100644 index a097783022a6b..0000000000000 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts +++ /dev/null @@ -1,34 +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 { SavedObjectsType } from '@kbn/core/server'; - -import { UPGRADE_ASSISTANT_TELEMETRY } from '../../common/constants'; -import { telemetrySavedObjectMigrations } from './migrations'; - -export const telemetrySavedObjectType: SavedObjectsType = { - name: UPGRADE_ASSISTANT_TELEMETRY, - hidden: false, - namespaceType: 'agnostic', - mappings: { - properties: { - features: { - properties: { - deprecation_logging: { - properties: { - enabled: { - type: 'boolean', - null_value: true, - }, - }, - }, - }, - }, - }, - }, - migrations: telemetrySavedObjectMigrations, -}; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts index 3ac2fdb93add8..ad2c33b079b0a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts @@ -5,20 +5,24 @@ * 2.0. */ -import { alertFieldMap } from '@kbn/alerting-plugin/common/alert_schema'; -import { mappingFromFieldMap } from '@kbn/alerting-plugin/common/alert_schema/field_maps/mapping_from_field_map'; +import { alertFieldMap, ecsFieldMap, legacyAlertFieldMap } from '@kbn/alerts-as-data-utils'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertsAsDataTest({ getService }: FtrProviderContext) { const es = getService('es'); - const commonFrameworkMappings = mappingFromFieldMap(alertFieldMap, 'strict'); + const frameworkMappings = mappingFromFieldMap(alertFieldMap, 'strict'); + const legacyAlertMappings = mappingFromFieldMap(legacyAlertFieldMap, 'strict'); + const ecsMappings = mappingFromFieldMap(ecsFieldMap, 'strict'); describe('alerts as data', () => { it('should install common alerts as data resources on startup', async () => { const ilmPolicyName = '.alerts-ilm-policy'; - const componentTemplateName = 'alerts-common-component-template'; + const frameworkComponentTemplateName = '.alerts-framework-mappings'; + const legacyComponentTemplateName = '.alerts-legacy-alert-mappings'; + const ecsComponentTemplateName = '.alerts-ecs-mappings'; const commonIlmPolicy = await es.ilm.getLifecycle({ name: ilmPolicyName, @@ -41,23 +45,65 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex }, }); - const { component_templates: componentTemplates } = await es.cluster.getComponentTemplate({ - name: componentTemplateName, + const { component_templates: componentTemplates1 } = await es.cluster.getComponentTemplate({ + name: frameworkComponentTemplateName, }); - expect(componentTemplates.length).to.eql(1); - const commonComponentTemplate = componentTemplates[0]; + expect(componentTemplates1.length).to.eql(1); + const frameworkComponentTemplate = componentTemplates1[0]; + + expect(frameworkComponentTemplate.name).to.eql(frameworkComponentTemplateName); + expect(frameworkComponentTemplate.component_template.template.mappings).to.eql( + frameworkMappings + ); + expect(frameworkComponentTemplate.component_template.template.settings).to.eql({ + index: { + number_of_shards: 1, + mapping: { + total_fields: { + limit: 1500, + }, + }, + }, + }); + + const { component_templates: componentTemplates2 } = await es.cluster.getComponentTemplate({ + name: legacyComponentTemplateName, + }); + + expect(componentTemplates2.length).to.eql(1); + const legacyComponentTemplate = componentTemplates2[0]; - expect(commonComponentTemplate.name).to.eql(componentTemplateName); - expect(commonComponentTemplate.component_template.template.mappings).to.eql( - commonFrameworkMappings + expect(legacyComponentTemplate.name).to.eql(legacyComponentTemplateName); + expect(legacyComponentTemplate.component_template.template.mappings).to.eql( + legacyAlertMappings ); - expect(commonComponentTemplate.component_template.template.settings).to.eql({ + expect(legacyComponentTemplate.component_template.template.settings).to.eql({ + index: { + number_of_shards: 1, + mapping: { + total_fields: { + limit: 1500, + }, + }, + }, + }); + + const { component_templates: componentTemplates3 } = await es.cluster.getComponentTemplate({ + name: ecsComponentTemplateName, + }); + + expect(componentTemplates3.length).to.eql(1); + const ecsComponentTemplate = componentTemplates3[0]; + + expect(ecsComponentTemplate.name).to.eql(ecsComponentTemplateName); + expect(ecsComponentTemplate.component_template.template.mappings).to.eql(ecsMappings); + expect(ecsComponentTemplate.component_template.template.settings).to.eql({ index: { number_of_shards: 1, mapping: { total_fields: { - limit: 100, + limit: 2500, }, }, }, @@ -65,7 +111,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex }); it('should install context specific alerts as data resources on startup', async () => { - const componentTemplateName = 'alerts-test.always-firing-component-template'; + const componentTemplateName = '.alerts-test.always-firing-mappings'; const indexTemplateName = '.alerts-test.always-firing-default-template'; const indexName = '.alerts-test.always-firing-default-000001'; const contextSpecificMappings = { @@ -98,7 +144,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex number_of_shards: 1, mapping: { total_fields: { - limit: 100, + limit: 1500, }, }, }, @@ -114,8 +160,8 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex '.alerts-test.always-firing-default-*', ]); expect(contextIndexTemplate.index_template.composed_of).to.eql([ - 'alerts-common-component-template', - 'alerts-test.always-firing-component-template', + '.alerts-test.always-firing-mappings', + '.alerts-framework-mappings', ]); expect(contextIndexTemplate.index_template.template!.mappings).to.eql({ dynamic: false, @@ -150,7 +196,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex dynamic: 'false', properties: { ...contextSpecificMappings, - ...commonFrameworkMappings.properties, + ...frameworkMappings.properties, }, }); 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/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 41b49ac35bfc6..123e7bdbed2e5 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 @@ -93,6 +93,84 @@ const getImportRuleBuffer = (connectorId: string) => { const buffer = Buffer.from(`${rule1String}\n`); return buffer; }; +const getImportRuleWithConnectorsBuffer = (connectorId: string) => { + const rule1 = { + id: '53aad690-544e-11ec-a349-11361cc441c4', + updated_at: '2021-12-03T15:33:13.271Z', + updated_by: 'elastic', + created_at: '2021-12-03T15:33:13.271Z', + created_by: 'elastic', + name: '7.16 test with action', + tags: [], + interval: '5m', + enabled: true, + description: 'test', + risk_score: 21, + severity: 'low', + license: '', + output_index: '', + meta: { from: '1m', kibana_siem_app_url: 'http://0.0.0.0:5601/s/7/app/security' }, + author: [], + false_positives: [], + from: 'now-360s', + rule_id: 'aa525d7c-8948-439f-b32d-27e00c750246', + max_signals: 100, + risk_score_mapping: [], + severity_mapping: [], + threat: [], + to: 'now', + references: [], + version: 1, + exceptions_list: [], + immutable: false, + type: 'query', + language: 'kuery', + index: [ + 'apm-*-transaction*', + 'traces-apm*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + query: '*:*', + filters: [], + throttle: '1h', + actions: [ + { + group: 'default', + id: connectorId, + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + action_type_id: '.slack', + }, + ], + }; + const connector = { + id: connectorId, + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.slack', + name: 'slack', + isMissingSecrets: false, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }; + const rule1String = JSON.stringify(rule1); + const connectorString = JSON.stringify(connector); + const buffer = Buffer.from(`${rule1String}\n${connectorString}`); + return buffer; +}; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -1115,95 +1193,179 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('importing a non-default-space 7.16 rule with a connector made in the non-default space should result in a 200', async () => { - const spaceId = '714-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.eql(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into the default space should result in a 404', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into a different non-default space should result in a 404', async () => { - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - it('importing a default-space 7.16 rule with a connector made in the default space into the default space should result in a 200', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + describe('should be imported into the non-default space', () => { + it('importing a non-default-space 7.16 rule with a connector made in the non-default space should result in a 200', async () => { + const spaceId = '714-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.eql(true); + expect(body.success_count).to.eql(1); + expect(body.errors.length).to.eql(0); + }); - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); + it('should import a non-default-space 7.16 rule with a connector made in the non-default space', async () => { + const spaceId = '714-space'; + const differentSpaceConnectorId = '5272d090-b111-11ed-b56a-a7991a8d8b32'; + + const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + it('should import a non-default-space 7.16 rule with a connector made in the non-default space into the default space successfully', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const differentSpaceConnectorId = '963ec960-a21a-11ed-84a4-a33e4c2558c9'; + const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + it('importing a non-default-space 7.16 rule with a connector made in the non-default space into the default space should result in a 404 if the file does not contain connectors', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` + ); + }); + // When objects become share-capable we will either add / update this test + it('importing a non-default-space 7.16 rule with a connector made in the non-default space into a different non-default space should result in a 404', async () => { + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` + ); + }); }); - it('importing a default-space 7.16 rule with a connector made in the default space into a non-default space should result in a 404', async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' - ); - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${defaultSpaceActionConnectorId}` - ); + describe('should be imported into the default space', () => { + it('should import a default-space 7.16 rule with a connector made in the default space into a non-default space successfully', async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' + ); + const defaultSpaceConnectorId = '8fbf6d10-a21a-11ed-84a4-a33e4c2558c9'; + + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleWithConnectorsBuffer(defaultSpaceConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + // When objects become share-capable we will either add / update this test + + it('importing a default-space 7.16 rule with a connector made in the default space into the default space should result in a 200', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(true); + expect(body.success_count).to.eql(1); + expect(body.errors.length).to.eql(0); + }); + it('importing a default-space 7.16 rule with a connector made in the default space into a non-default space should result in a 404', async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' + ); + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${defaultSpaceActionConnectorId}` + ); + }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts index fe12454cf4591..099104ad411f7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts @@ -64,7 +64,8 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'ml-rule-id', }; - describe('Machine learning type rules', () => { + // FLAKY: https://github.com/elastic/kibana/issues/145776 + describe.skip('Machine learning type rules', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start 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..0bcb05b5e1c40 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 @@ -323,5 +323,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/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 2dd546511dd9a..571a0fee11f54 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -37,7 +37,7 @@ export default function (providerContext: FtrProviderContext) { describe('reassign single agent', () => { it('should allow to reassign single agent', async () => { await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', @@ -49,7 +49,7 @@ export default function (providerContext: FtrProviderContext) { it('should throw an error for invalid policy id for single reassign', async () => { await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'INVALID_ID', @@ -61,7 +61,7 @@ export default function (providerContext: FtrProviderContext) { // policy2 is not hosted // reassign succeeds await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', @@ -79,7 +79,7 @@ export default function (providerContext: FtrProviderContext) { // reassign fails await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', 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/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/osquery_cypress/agent.ts b/x-pack/test/osquery_cypress/agent.ts index 2bdecd90efdff..e35b2f1bc4d37 100644 --- a/x-pack/test/osquery_cypress/agent.ts +++ b/x-pack/test/osquery_cypress/agent.ts @@ -7,7 +7,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosRequestConfig } from 'axios'; -import { ChildProcess, spawn } from 'child_process'; +import execa from 'execa'; import { getLatestVersion } from './artifact_manager'; import { Manager } from './resource_manager'; @@ -22,7 +22,7 @@ export interface AgentManagerParams { export class AgentManager extends Manager { private params: AgentManagerParams; private log: ToolingLog; - private agentProcess?: ChildProcess; + private agentContainerId?: string; private requestOptions: AxiosRequestConfig; constructor(params: AgentManagerParams, log: ToolingLog, requestOptions: AxiosRequestConfig) { super(); @@ -33,34 +33,40 @@ export class AgentManager extends Manager { public async setup() { this.log.info('Running agent preconfig'); - return await axios.post( - `${this.params.kibanaUrl}/api/fleet/agents/setup`, - {}, + + await axios.post( + `${this.params.kibanaUrl}/api/fleet/agent_policies?sys_monitoring=true`, + { + name: 'Osquery policy', + description: '', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + inactivity_timeout: 1209600, + }, this.requestOptions ); - } - public async startAgent() { this.log.info('Getting agent enrollment key'); const { data: apiKeys } = await axios.get( this.params.kibanaUrl + '/api/fleet/enrollment_api_keys', this.requestOptions ); - const policy = apiKeys.items[1]; + const policy = apiKeys.items[0]; this.log.info('Running the agent'); const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; this.log.info(artifact); - const args = [ + const dockerArgs = [ 'run', + '--detach', '--add-host', 'host.docker.internal:host-gateway', '--env', 'FLEET_ENROLL=1', '--env', - `FLEET_URL=http://host.docker.internal:8220`, + `FLEET_URL=https://host.docker.internal:8220`, '--env', `FLEET_ENROLLMENT_TOKEN=${policy.api_key}`, '--env', @@ -69,7 +75,7 @@ export class AgentManager extends Manager { artifact, ]; - this.agentProcess = spawn('docker', args, { stdio: 'inherit' }); + this.agentContainerId = (await execa('docker', dockerArgs)).stdout; // Wait til we see the agent is online let done = false; @@ -92,15 +98,11 @@ export class AgentManager extends Manager { protected _cleanup() { this.log.info('Cleaning up the agent process'); - if (this.agentProcess) { - if (!this.agentProcess.kill(9)) { - this.log.warning('Unable to kill agent process'); - } + if (this.agentContainerId) { + this.log.info('Closing agent process'); - this.agentProcess.on('close', () => { - this.log.info('Agent process closed'); - }); - delete this.agentProcess; + execa.sync('docker', ['kill', this.agentContainerId]); + this.log.info('Agent process closed'); } return; } diff --git a/x-pack/test/osquery_cypress/artifact_manager.ts b/x-pack/test/osquery_cypress/artifact_manager.ts index 7ee2680e21f83..2a520c22203c0 100644 --- a/x-pack/test/osquery_cypress/artifact_manager.ts +++ b/x-pack/test/osquery_cypress/artifact_manager.ts @@ -6,5 +6,5 @@ */ export async function getLatestVersion(): Promise { - return '8.6.0-SNAPSHOT'; + return '8.8.0-SNAPSHOT'; } diff --git a/x-pack/test/osquery_cypress/config.ts b/x-pack/test/osquery_cypress/config.ts index 37f7b3f63b36c..76b5c3eca9f89 100644 --- a/x-pack/test/osquery_cypress/config.ts +++ b/x-pack/test/osquery_cypress/config.ts @@ -35,9 +35,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalTestsConfig.get('kbnTestServer'), serverArgs: [ ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.warnLegacyBrowsers=false', '--csp.strict=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + `--xpack.fleet.agents.fleet_server.hosts=["https://host.docker.internal:8220"]`, `--xpack.fleet.agents.elasticsearch.host=http://host.docker.internal:${kibanaCommonTestsConfig.get( 'servers.elasticsearch.port' )}`, diff --git a/x-pack/test/osquery_cypress/fleet_server.ts b/x-pack/test/osquery_cypress/fleet_server.ts index 77ec56cf20960..ce1ff24adc32a 100644 --- a/x-pack/test/osquery_cypress/fleet_server.ts +++ b/x-pack/test/osquery_cypress/fleet_server.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { ChildProcess, spawn } from 'child_process'; import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosRequestConfig } from 'axios'; +import execa from 'execa'; import { Manager } from './resource_manager'; import { getLatestVersion } from './artifact_manager'; import { AgentManagerParams } from './agent'; export class FleetManager extends Manager { - private fleetProcess?: ChildProcess; + private fleetContainerId?: string; private config: AgentManagerParams; private log: ToolingLog; private requestOptions: AxiosRequestConfig; @@ -25,77 +25,65 @@ export class FleetManager extends Manager { } public async setup(): Promise { this.log.info('Setting fleet up'); - return new Promise(async (res, rej) => { - try { - // default fleet server policy no longer created by default - const { - data: { - item: { id: policyId }, - }, - } = await axios.post( - `${this.config.kibanaUrl}/api/fleet/agent_policies`, - { - name: 'Default Fleet Server policy', - description: '', - namespace: 'default', - monitoring_enabled: ['logs', 'metrics'], - has_fleet_server: true, - }, - this.requestOptions - ); - const response = await axios.post( - `${this.config.kibanaUrl}/api/fleet/service_tokens`, - {}, - this.requestOptions - ); - const serviceToken = response.data.value; - const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; - this.log.info(artifact); + // default fleet server policy no longer created by default + const { + data: { + item: { id: policyId }, + }, + } = await axios.post( + `${this.config.kibanaUrl}/api/fleet/agent_policies`, + { + name: 'Default Fleet Server policy', + description: '', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + has_fleet_server: true, + }, + this.requestOptions + ); - const host = 'host.docker.internal'; + const response = await axios.post( + `${this.config.kibanaUrl}/api/fleet/service_tokens`, + {}, + this.requestOptions + ); + const serviceToken = response.data.value; + const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; + this.log.info(artifact); - const args = [ - 'run', - '-p', - `8220:8220`, - '--add-host', - 'host.docker.internal:host-gateway', - '--env', - 'FLEET_SERVER_ENABLE=true', - '--env', - `FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.config.esPort}`, - '--env', - `FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`, - '--env', - `FLEET_SERVER_POLICY=${policyId}`, - '--rm', - artifact, - ]; - this.log.info('docker ' + args.join(' ')); - this.fleetProcess = spawn('docker', args, { - stdio: 'inherit', - }); - this.fleetProcess.on('error', rej); - setTimeout(res, 15000); - } catch (error) { - rej(error); - } - }); + const host = 'host.docker.internal'; + + const dockerArgs = [ + 'run', + '--detach', + '-p', + `8220:8220`, + '--add-host', + 'host.docker.internal:host-gateway', + '--env', + 'FLEET_SERVER_ENABLE=true', + '--env', + `FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.config.esPort}`, + '--env', + `FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`, + '--env', + `FLEET_SERVER_POLICY=${policyId}`, + '--rm', + artifact, + ]; + + this.log.info('docker ' + dockerArgs.join(' ')); + this.fleetContainerId = (await execa('docker', dockerArgs)).stdout; } protected _cleanup() { this.log.info('Removing old fleet config'); - if (this.fleetProcess) { + if (this.fleetContainerId) { this.log.info('Closing fleet process'); - if (!this.fleetProcess.kill(9)) { - this.log.warning('Unable to kill fleet server process'); - } - this.fleetProcess.on('close', () => { - this.log.info('Fleet server process closed'); - }); - delete this.fleetProcess; + execa.sync('docker', ['kill', this.fleetContainerId]); + this.log.info('Fleet server process closed'); } } } diff --git a/x-pack/test/osquery_cypress/runner.ts b/x-pack/test/osquery_cypress/runner.ts index 2c97d08b682c3..2cd194c14ea53 100644 --- a/x-pack/test/osquery_cypress/runner.ts +++ b/x-pack/test/osquery_cypress/runner.ts @@ -12,10 +12,7 @@ import { withProcRunner } from '@kbn/dev-proc-runner'; import { FtrProviderContext } from './ftr_provider_context'; -import { - // AgentManager, - AgentManagerParams, -} from './agent'; +import { AgentManager, AgentManagerParams } from './agent'; import { FleetManager } from './fleet_server'; async function withFleetAgent( @@ -47,8 +44,7 @@ async function withFleetAgent( }, }; const fleetManager = new FleetManager(params, log, requestOptions); - - // const agentManager = new AgentManager(params, log, requestOptions); + const agentManager = new AgentManager(params, log, requestOptions); // Since the managers will create uncaughtException event handlers we need to exit manually process.on('uncaughtException', (err) => { @@ -57,13 +53,13 @@ async function withFleetAgent( process.exit(1); }); - // await agentManager.setup(); await fleetManager.setup(); + await agentManager.setup(); try { await runner({}); } finally { + agentManager.cleanup(); fleetManager.cleanup(); - // agentManager.cleanup(); } } diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts index 02765aa8c2a77..b62cf7b39965c 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts @@ -10,7 +10,7 @@ import type { ElasticsearchClient, Logger, LogMeta } from '@kbn/core/server'; import sinon from 'sinon'; import { v4 as uuidv4 } from 'uuid'; import expect from '@kbn/expect'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { AlertConsumers, ALERT_REASON, diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts index dc5752417bfb4..10351fc6cf2ef 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts @@ -9,7 +9,7 @@ import { type Subject, ReplaySubject } from 'rxjs'; import type { ElasticsearchClient, Logger, LogMeta } from '@kbn/core/server'; import sinon from 'sinon'; import expect from '@kbn/expect'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { AlertConsumers, ALERT_REASON, 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/tsconfig.json b/x-pack/test/tsconfig.json index 476b6223b9591..be736ec73fd0d 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -115,6 +115,7 @@ "@kbn/cloud-security-posture-plugin", "@kbn/cloud-integration-saml-provider-plugin", "@kbn/security-api-integration-helpers", + "@kbn/alerts-as-data-utils", "@kbn/discover-plugin", ] } diff --git a/yarn.lock b/yarn.lock index abe45629cea82..7ee742222ff9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,12 +7,13 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== -"@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== +"@ampproject/remapping@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" "@apidevtools/json-schema-ref-parser@^9.0.6": version "9.0.9" @@ -56,12 +57,12 @@ resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== -"@babel/cli@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.20.7.tgz#8fc12e85c744a1a617680eacb488fab1fcd35b7c" - integrity sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ== +"@babel/cli@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.21.0.tgz#1868eb70e9824b427fc607610cce8e9e7889e7e1" + integrity sha512-xi7CxyS8XjSyiwUGCfwf+brtJxjW1/ZTcBUkP10xawIEXLX5HzLn+3aXkgxozcP2UhRhtKTmQurw9Uaes7jZrA== dependencies: - "@jridgewell/trace-mapping" "^0.3.8" + "@jridgewell/trace-mapping" "^0.3.17" commander "^4.0.1" convert-source-map "^1.1.0" fs-readdir-recursive "^1.1.0" @@ -113,21 +114,21 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.20.12", "@babel/core@^7.7.2", "@babel/core@^7.7.5": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" - integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== +"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.21.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" + integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== dependencies: - "@ampproject/remapping" "^2.1.0" + "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.0" "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helpers" "^7.20.7" - "@babel/parser" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.0" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.0" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.12" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -150,13 +151,14 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.20.14", "@babel/generator@^7.20.7", "@babel/generator@^7.7.2": - version "7.20.14" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" - integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.21.0", "@babel/generator@^7.21.1", "@babel/generator@^7.7.2": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" + integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.21.0" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6": @@ -185,17 +187,18 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0", "@babel/helper-create-class-features-plugin@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2" - integrity sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2", "@babel/helper-create-class-features-plugin@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9" + integrity sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": @@ -244,13 +247,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -259,12 +262,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" + integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.0" "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -273,10 +276,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" - integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.21.0": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" @@ -284,8 +287,8 @@ "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.10" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -314,16 +317,17 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": version "7.20.2" @@ -356,10 +360,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" + integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== "@babel/helper-wrap-function@^7.18.9": version "7.19.0" @@ -371,14 +375,14 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" - integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -389,10 +393,10 @@ 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.13", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7": - version "7.20.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" - integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== +"@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": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" + integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -532,10 +536,10 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" - integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== +"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" @@ -968,13 +972,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== +"@babel/plugin-transform-runtime@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz#2a884f29556d0a68cd3d152dcc9e6c71dfb6eee8" + integrity sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1016,13 +1020,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.18.6": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz#2c7ec62b8bfc21482f3748789ba294a46a375169" - integrity sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw== +"@babel/plugin-transform-typescript@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz#f0956a153679e3b377ae5b7f0143427151e4c848" + integrity sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-escapes@^7.18.10": @@ -1153,19 +1157,19 @@ "@babel/plugin-transform-react-jsx-development" "^7.18.6" "@babel/plugin-transform-react-pure-annotations" "^7.18.6" -"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== +"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz#bcbbca513e8213691fe5d4b23d9251e01f00ebff" + integrity sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-transform-typescript" "^7.21.0" -"@babel/register@^7.12.1", "@babel/register@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.9.tgz#1888b24bc28d5cc41c412feb015e9ff6b96e439c" - integrity sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw== +"@babel/register@^7.12.1", "@babel/register@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.21.0.tgz#c97bf56c2472e063774f31d344c592ebdcefa132" + integrity sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1181,10 +1185,10 @@ core-js-pure "^3.25.1" regenerator-runtime "^0.13.10" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" - integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== dependencies: regenerator-runtime "^0.13.11" @@ -1197,26 +1201,26 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" - integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== +"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" + integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.1" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.13" - "@babel/types" "^7.20.7" + "@babel/parser" "^7.21.2" + "@babel/types" "^7.21.2" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" - integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" + integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1539,10 +1543,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@75.1.0": - version "75.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-75.1.0.tgz#6bdb2a12e5dd503258e74d5585803f52b826b83e" - integrity sha512-HJgoARNsXeYDIGO9sKV+wwfmFA2IKL9hjOMj8B0PZ4fA6Euprw7KPLkakUbwjTCm0rqYUf/6zmXRafvzvdKLmA== +"@elastic/eui@75.1.2": + version "75.1.2" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-75.1.2.tgz#c8ccb1728162b131e49a16833468ab2b0228f1bf" + integrity sha512-J6u16NR3BD5snje2CSWnk+JvEQ7y/8tzpmi2Ul+WWfzQwvf7DsKtouSIs91jdzC1QGSN26S1D3wKZvzaszXacg== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -2680,6 +2684,14 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -2689,15 +2701,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/source-map@^0.3.2": version "0.3.2" @@ -2707,10 +2719,10 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -2720,13 +2732,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.0", "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" "@jsdevtools/ono@^7.1.3": version "7.1.3" @@ -2785,6 +2797,10 @@ version "0.0.0" uid "" +"@kbn/alerts-as-data-utils@link:packages/kbn-alerts-as-data-utils": + version "0.0.0" + uid "" + "@kbn/alerts-restricted-fixtures-plugin@link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted": version "0.0.0" uid "" @@ -3041,6 +3057,10 @@ version "0.0.0" uid "" +"@kbn/content-management-examples-plugin@link:examples/content_management_examples": + version "0.0.0" + uid "" + "@kbn/content-management-plugin@link:src/plugins/content_management": version "0.0.0" uid "" @@ -4061,6 +4081,10 @@ version "0.0.0" uid "" +"@kbn/expandable-flyout@link:packages/kbn-expandable-flyout": + version "0.0.0" + uid "" + "@kbn/expect@link:packages/kbn-expect": version "0.0.0" uid "" @@ -17573,9 +17597,9 @@ inquirer@^8.2.3: wrap-ansi "^7.0.0" install-artifact-from-github@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.1.tgz#eefaad9af35d632e5d912ad1569c1de38c3c2462" - integrity sha512-3l3Bymg2eKDsN5wQuMfgGEj2x6l5MCAv0zPL6rxHESufFVlEAKW/6oY9F1aGgvY/EgWm5+eWGRjINveL4X7Hgg== + version "1.3.2" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.2.tgz#1a16d9508e40330523a3017ae0d4713ccc64de82" + integrity sha512-yCFcLvqk0yQdxx0uJz4t9Z3adDMLAYrcGYv546uRXCSvxE+GqNYhhz/KmrGcUKGI/gVLR9n/e/zM9jX/+ASMJQ== internal-slot@^1.0.3: version "1.0.3" @@ -25493,10 +25517,10 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.8.0.tgz#386d57f23fe8edf5178f5bd06aae9ffaffbcb692" - integrity sha512-s/HL8WNwy1ggHR244+tAhjhyKMJnZLt1HKJ6Gn7nQgVjB/ybDF+46Uui0qI2J7AjPNJzlUmTncdC/jg/kKkn0A== +selenium-webdriver@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.8.1.tgz#4b0a546c4ea747c44e9688c108f7a46b8d8244ab" + integrity sha512-p4MtfhCQdcV6xxkS7eI0tQN6+WNReRULLCAuT4RDGkrjfObBNXMJ3WT8XdK+aXTr5nnBKuh+PxIevM0EjJgkxA== dependencies: jszip "^3.10.0" tmp "^0.2.1" @@ -27106,10 +27130,10 @@ 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.3, terser@^5.3.4, terser@^5.9.0: - version "5.16.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.3.tgz#3266017a9b682edfe019b8ecddd2abaae7b39c6b" - integrity sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q== +terser@^5.14.1, terser@^5.16.4, 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== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0"