diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index c83af688ed47f..518192fbe05f3 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -232,6 +232,9 @@ enabled: - x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts + - x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts + - x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts + - x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/examples/config.ts - x-pack/test/fleet_api_integration/config.agent.ts diff --git a/.buildkite/pipelines/serverless.yml b/.buildkite/pipelines/serverless.yml index ec09f77e751ec..f4d7d0d49469b 100644 --- a/.buildkite/pipelines/serverless.yml +++ b/.buildkite/pipelines/serverless.yml @@ -21,19 +21,6 @@ steps: - exit_status: '-1' limit: 3 - - command: SERVERLESS_ENVIRONMENT=common .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Common Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - command: SERVERLESS_ENVIRONMENT=observability .buildkite/scripts/steps/functional/serverless_ftr.sh label: 'Serverless Observability Tests' agents: diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index 70a06d1d97e8d..98bd47597bd3e 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -60,6 +60,7 @@ const uploadPipeline = (pipelineContent: string | object) => { if ( (await doAnyChangesMatch([ + /^src\/plugins\/controls/, /^packages\/kbn-securitysolution-.*/, /^x-pack\/plugins\/lists/, /^x-pack\/plugins\/security_solution/, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9f41080bd5a59..4248bd86c7d5b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -468,6 +468,7 @@ examples/locator_examples @elastic/kibana-app-services examples/locator_explorer @elastic/kibana-app-services packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core +x-pack/plugins/logs_shared @elastic/infra-monitoring-ui x-pack/plugins/logstash @elastic/logstash packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 6e7c474cacd7a..458a5e8c9a600 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-07-03 +date: 2023-07-05 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 fd3ff9831d794..d9b48790601d4 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-07-03 +date: 2023-07-05 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 ef98513eae8af..af01029abd03c 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-07-03 +date: 2023-07-05 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 67ed79d64cdc4..c967aa3c52c1b 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -3152,7 +3152,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -3168,7 +3168,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 15a204ae04874..e676fa9012982 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index c14f27ab3bedc..bb1ae41c78010 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index e6f19ae5a0655..ab465634b7fad 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 897a78bb219c3..02eaf1c996052 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-07-03 +date: 2023-07-05 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 44c2cbb782545..88a9a1a56b539 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-07-03 +date: 2023-07-05 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 75b92e187bcf1..33b9d541849d5 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-07-03 +date: 2023-07-05 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 c6dd67e43d07e..732a105f75e1a 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-07-03 +date: 2023-07-05 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 479c3cdeca646..7ec5590bc8cea 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-07-03 +date: 2023-07-05 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 554372c5a7ed3..750dbc1f0843d 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-07-03 +date: 2023-07-05 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 4de2551a154c6..d1e2e01d8026d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_chat_provider.mdx b/api_docs/cloud_chat_provider.mdx index d1205180fca8e..d56a91c08416a 100644 --- a/api_docs/cloud_chat_provider.mdx +++ b/api_docs/cloud_chat_provider.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChatProvider title: "cloudChatProvider" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChatProvider plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChatProvider'] --- import cloudChatProviderObj from './cloud_chat_provider.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index c2dc31f4d57a0..0f5aa80fa2e39 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 55ba690827802..bbab50b2fcb67 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index d9730ff4cf9e8..864b49b9dca8d 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-07-03 +date: 2023-07-05 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 19be173117b07..30f67a44a0545 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-07-03 +date: 2023-07-05 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 176380ce83ac1..758799ddfb73f 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index b22e9513e94db..d191620df4054 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 2268e61817c72..1a6ed1ab648bf 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-07-03 +date: 2023-07-05 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 299103af8c7ec..48fb0d4a13824 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-07-03 +date: 2023-07-05 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 16a417cacb7a5..5ad5c977a1182 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-07-03 +date: 2023-07-05 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 9ff6f2330f88b..9899d1c60a960 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-07-03 +date: 2023-07-05 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 6e1ba8e0ec9f5..d46b463e72cfa 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13292,10 +13292,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" @@ -16822,7 +16818,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -16864,10 +16860,8 @@ "id": "def-server.DataViewsService.createAndSave.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists." - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -16931,7 +16925,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -16973,10 +16967,8 @@ "id": "def-server.DataViewsService.createSavedObject.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -20951,10 +20943,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" @@ -25352,7 +25340,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -25394,10 +25382,8 @@ "id": "def-common.DataViewsService.createAndSave.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists." - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -25461,7 +25447,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -25503,10 +25489,8 @@ "id": "def-common.DataViewsService.createSavedObject.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -28342,7 +28326,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -28358,7 +28342,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 9551b7cebe961..604c99e7e8a77 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3303 | 119 | 2579 | 27 | +| 3303 | 119 | 2583 | 27 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 9bb77627bfcaa..6e46268866697 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3303 | 119 | 2579 | 27 | +| 3303 | 119 | 2583 | 27 | ## Client diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 0f93446b3f05a..9806722c48786 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3303 | 119 | 2579 | 27 | +| 3303 | 119 | 2583 | 27 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 3e737d288086c..1f9bc9babfa75 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-07-03 +date: 2023-07-05 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 1ae9fad417e5d..b9fa2718553ca 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-07-03 +date: 2023-07-05 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 37f9b4946d837..4d03b9127920b 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index c88fad679a3bb..96ce68eb2e53c 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -111,10 +111,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" @@ -4926,7 +4922,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -4968,10 +4964,8 @@ "id": "def-public.DataViewsService.createAndSave.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists." - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -5035,7 +5029,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -5077,10 +5071,8 @@ "id": "def-public.DataViewsService.createSavedObject.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -7804,7 +7796,7 @@ "section": "def-common.DataViewAttributes", "text": "DataViewAttributes" }, - ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; }) => Promise<", + ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; overwrite?: boolean | undefined; }) => Promise<", { "pluginId": "@kbn/core-saved-objects-common", "scope": "common", @@ -7879,6 +7871,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-public.SavedObjectsClientCommon.create.$2.overwrite", + "type": "CompoundType", + "tags": [], + "label": "overwrite", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ] } @@ -8420,10 +8426,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" @@ -12276,7 +12278,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -12318,10 +12320,8 @@ "id": "def-server.DataViewsService.createAndSave.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists." - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -12385,7 +12385,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -12427,10 +12427,8 @@ "id": "def-server.DataViewsService.createSavedObject.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -13877,7 +13875,7 @@ "section": "def-common.DataViewAttributes", "text": "DataViewAttributes" }, - ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; }) => Promise<", + ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; overwrite?: boolean | undefined; }) => Promise<", { "pluginId": "@kbn/core-saved-objects-common", "scope": "common", @@ -13952,6 +13950,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-server.SavedObjectsClientCommon.create.$2.overwrite", + "type": "CompoundType", + "tags": [], + "label": "overwrite", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ] } @@ -15784,10 +15796,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" @@ -20304,7 +20312,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -20346,10 +20354,8 @@ "id": "def-common.DataViewsService.createAndSave.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists." - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -20413,7 +20419,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -20455,10 +20461,8 @@ "id": "def-common.DataViewsService.createSavedObject.$2", "type": "boolean", "tags": [], - "label": "override", - "description": [ - "Overwrite if existing index pattern exists" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean" ], @@ -22201,7 +22205,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean | undefined, displayErrors?: boolean | undefined) => Promise<", + ", overwrite?: boolean | undefined, displayErrors?: boolean | undefined) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -22241,10 +22245,8 @@ "id": "def-common.DataViewsServicePublicMethods.createSavedObject.$2", "type": "CompoundType", "tags": [], - "label": "override", - "description": [ - "- If true, save over existing data view" - ], + "label": "overwrite", + "description": [], "signature": [ "boolean | undefined" ], @@ -25117,7 +25119,7 @@ "section": "def-common.DataViewAttributes", "text": "DataViewAttributes" }, - ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; }) => Promise<", + ", options: { id?: string | undefined; initialNamespaces?: string[] | undefined; overwrite?: boolean | undefined; }) => Promise<", { "pluginId": "@kbn/core-saved-objects-common", "scope": "common", @@ -25192,6 +25194,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-common.SavedObjectsClientCommon.create.$2.overwrite", + "type": "CompoundType", + "tags": [], + "label": "overwrite", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ] } @@ -25877,7 +25893,7 @@ "section": "def-common.DataViewSpec", "text": "DataViewSpec" }, - ", override?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", @@ -25893,7 +25909,7 @@ "section": "def-common.DataView", "text": "DataView" }, - ", override?: boolean, displayErrors?: boolean) => Promise<", + ", overwrite?: boolean, displayErrors?: boolean) => Promise<", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index fa611f10b8e36..eaa8b1d45813e 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1048 | 0 | 258 | 2 | +| 1051 | 0 | 268 | 2 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index bda216ec62175..80d4eef21696b 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-07-03 +date: 2023-07-05 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 550f6f6816309..1308d5a4c7eca 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index db32045f12817..9b3c748b49241 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -1141,12 +1141,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion)+ 12 more | - | | | [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion)+ 78 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title)+ 24 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title)+ 22 more | - | | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title)+ 24 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title)+ 7 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title)+ 22 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title)+ 6 more | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24) | 8.8.0 | @@ -1219,7 +1219,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [message_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/tls_rule/message_utils.ts#:~:text=alertFactory), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/common.ts#:~:text=alertFactory), [tls_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule.ts#:~:text=alertFactory), [monitor_status_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts#:~:text=alertFactory) | - | | | [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx#:~:text=indexPatternId) | - | | | [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks) | - | -| | [actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts#:~:text=SavedObject), [actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts#:~:text=SavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts#:~:text=SavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts#:~:text=SavedObject), [effects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts#:~:text=SavedObject)+ 1 more | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts#:~:text=SavedObject), [effects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts#:~:text=SavedObject), [effects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts#:~:text=SavedObject) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 6c7f4a1b499f3..dcd422b44a67f 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 96ffcfe43d38d..1290792db63e6 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-07-03 +date: 2023-07-05 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 c1d76eb83346c..894901004224e 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-07-03 +date: 2023-07-05 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 2b842e492a9b6..545838989fcef 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-07-03 +date: 2023-07-05 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 04d584f7282f9..456815253add3 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 9f214e29aca76..3c60597cb2aa2 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index c657b33310409..3edd67dc167ff 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-07-03 +date: 2023-07-05 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 87bec77293be6..ab3b8aef1a4aa 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-07-03 +date: 2023-07-05 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 0757a18e10a19..99cf9558d581d 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-07-03 +date: 2023-07-05 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 8dcd203f43d13..3ce1723b5ef7d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/ess_security.mdx b/api_docs/ess_security.mdx index 856ed0b67c624..34a28e08b34e8 100644 --- a/api_docs/ess_security.mdx +++ b/api_docs/ess_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/essSecurity title: "essSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the essSecurity plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'essSecurity'] --- import essSecurityObj from './ess_security.devdocs.json'; diff --git a/api_docs/event_annotation.devdocs.json b/api_docs/event_annotation.devdocs.json index 3b205ea85a472..98e46a8059562 100644 --- a/api_docs/event_annotation.devdocs.json +++ b/api_docs/event_annotation.devdocs.json @@ -310,6 +310,38 @@ ], "returnComment": [] }, + { + "parentPluginId": "eventAnnotation", + "id": "def-public.EventAnnotationServiceType.groupExistsWithTitle", + "type": "Function", + "tags": [], + "label": "groupExistsWithTitle", + "description": [], + "signature": [ + "(title: string) => Promise" + ], + "path": "src/plugins/event_annotation/public/event_annotation_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotation", + "id": "def-public.EventAnnotationServiceType.groupExistsWithTitle.$1", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/event_annotation/public/event_annotation_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "eventAnnotation", "id": "def-public.EventAnnotationServiceType.findAnnotationGroupContent", diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index a6ba105b7a6a2..44608654af5f9 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 234 | 30 | 234 | 4 | +| 236 | 30 | 236 | 4 | ## Client diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index bfd0d8f64ebfe..e194316b7cea7 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 0e550c333dc30..3f0fc93227986 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 62c5aed54bee2..6f91122bf3972 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-07-03 +date: 2023-07-05 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 25ca29169fd79..8f1352f3dd437 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-07-03 +date: 2023-07-05 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 aa3ecef0635b6..1b2d4b7cd7932 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-07-03 +date: 2023-07-05 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 cb9cf1e79b81e..7a430e93b5ca0 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-07-03 +date: 2023-07-05 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 bd845b069beef..06598f58ad8c5 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-07-03 +date: 2023-07-05 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 4790a1440a31c..4b1bb925243c7 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-07-03 +date: 2023-07-05 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 a5fda431f88e6..9cf2c80067ba2 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-07-03 +date: 2023-07-05 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 6dc2d107f845c..bb2f0bec92f7f 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-07-03 +date: 2023-07-05 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 c4b99bdb1cd89..2a403a81ce883 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-07-03 +date: 2023-07-05 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 b4e92499d5a59..78fc76e45c6d6 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-07-03 +date: 2023-07-05 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 a8212bf874358..5780d1614c206 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-07-03 +date: 2023-07-05 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 05d7b916e4d52..bf4a68d0decfb 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-07-03 +date: 2023-07-05 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 2f41edf8ec728..8f8e4b5650720 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-07-03 +date: 2023-07-05 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 4f29157f0d332..2e145b784ee3a 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-07-03 +date: 2023-07-05 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 f6d4c24f36bb9..a412b86858f8d 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-07-03 +date: 2023-07-05 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 2838e587f6f9f..064abd699fcc1 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-07-03 +date: 2023-07-05 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 dba63ce83efd0..49cd766fb154c 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 6db8d183a55ce..a12f1527b7bd6 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index cc9fa3ccf9895..caa3f6999359d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index e3561ff67f72f..8208e3d11b0a1 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 7fecdc4a0cf48..453c2c521f93b 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-07-03 +date: 2023-07-05 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 693fb5e1bc6a2..8ced8b4d94d65 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-07-03 +date: 2023-07-05 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 066cbf18f7bfe..a26b1a8bfb7a3 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-07-03 +date: 2023-07-05 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 292d0a0ee2565..7cfa3c26ffc0f 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-07-03 +date: 2023-07-05 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 489440e7892ee..f18e468e26e83 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-07-03 +date: 2023-07-05 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 13174c35a6298..9405afd666b00 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-07-03 +date: 2023-07-05 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 520d1eb1b99cf..eb30834e29d5e 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-07-03 +date: 2023-07-05 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 c669489aceb7b..19168524c29ba 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-07-03 +date: 2023-07-05 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 ace377408281a..67e1b7f02158f 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-07-03 +date: 2023-07-05 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 3510ccb3026ed..45cf012204d6e 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-07-03 +date: 2023-07-05 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 d8b747beb9973..c17512a5f0b68 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-07-03 +date: 2023-07-05 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 073c7ad634370..00832a2176ad9 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 3b07e03c7e8f7..81f5d6b6ecabe 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index a89fadba6b905..af7edc0d8a497 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index f8116af96a29c..55907ec366571 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-07-03 +date: 2023-07-05 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 6549daceac9df..7291da859af3a 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-07-03 +date: 2023-07-05 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 487971dc0824d..d4b446a4feb9f 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-07-03 +date: 2023-07-05 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 b84ca2a9ae3d6..cd833eb9c0c7d 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-07-03 +date: 2023-07-05 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 d835ce46020aa..a80d36504c5f8 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-07-03 +date: 2023-07-05 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 d8ef06e92a2d4..0a11917654e18 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-07-03 +date: 2023-07-05 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 34aae13e5f909..989a7c56c4e02 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-07-03 +date: 2023-07-05 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 5903db6c22eb4..653fb639d5118 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-07-03 +date: 2023-07-05 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 ff477d2694ad9..71f6d6887ca2f 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-07-03 +date: 2023-07-05 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 d7c82e20ac204..90eeda70308da 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index ddecff7dcdc3a..a061cd8d5b950 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-07-03 +date: 2023-07-05 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 dfe6f14649adf..7169cbfa902dc 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-07-03 +date: 2023-07-05 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 7cf209d3a67e8..0d4bd7daabd64 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-07-03 +date: 2023-07-05 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 929302a536fb9..6aec3f4c21c56 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-07-03 +date: 2023-07-05 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 5d822ead90c1b..39aa5e354040a 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-07-03 +date: 2023-07-05 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 2634b8ff3bd29..845c13ed3d5ff 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-07-03 +date: 2023-07-05 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 8ede10441d779..922efdf36bebf 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-07-03 +date: 2023-07-05 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 52f8c815893b1..ccb99fbe2c75a 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-07-03 +date: 2023-07-05 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 b4c0a5bc34885..6080c4b114768 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-07-03 +date: 2023-07-05 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 086ac5b45494a..c410b8266abdb 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-07-03 +date: 2023-07-05 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 414873ca5e0c2..c6ad6c25c5899 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-07-03 +date: 2023-07-05 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 ed494fbbffdbe..94e1ccec124a0 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-07-03 +date: 2023-07-05 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 a8fcbb433416b..1215355cd87c7 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-07-03 +date: 2023-07-05 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 8a8436cfca4f5..3984da8b0fc33 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-07-03 +date: 2023-07-05 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 fb82faf4a7a27..8a2db1c927fb4 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-07-03 +date: 2023-07-05 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 3455e1e02089c..462dfab3c9a2c 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-07-03 +date: 2023-07-05 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 c62ef76a639b5..cdf1e277d3d40 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-07-03 +date: 2023-07-05 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 e31d1f88a6d91..70f1c79666d8f 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-07-03 +date: 2023-07-05 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_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index 22d2d4f11a703..936c643770e70 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index 385c8b7b37062..49f1f8754acaa 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index feded3fb5194a..4f96ce3f3326d 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index db392a7e558a2..890c6416b9405 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index f04fb919cc799..6ceaee7d6b2ef 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-07-03 +date: 2023-07-05 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 9b4fc1de7b358..51c64ffe9dd2d 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-07-03 +date: 2023-07-05 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 9b46410b4c5d5..5bf8c63c71e01 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-07-03 +date: 2023-07-05 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 f6024026e107b..651a6e4da1abb 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-07-03 +date: 2023-07-05 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 ea4cd542ba79f..5407846be8d2e 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-07-03 +date: 2023-07-05 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 7b7474182accc..48a3087decd9f 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-07-03 +date: 2023-07-05 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 45eafb44bfc20..eb2314d674462 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-07-03 +date: 2023-07-05 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 2ef325b1161b0..1c35027756a2f 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-07-03 +date: 2023-07-05 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 dc7c24e327b29..b73ae9f06c746 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-07-03 +date: 2023-07-05 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 8a4c00d3d5b3d..2a67df9211aa2 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-07-03 +date: 2023-07-05 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 db198460df556..d2a25f38fb4d2 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-07-03 +date: 2023-07-05 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 d3179020d8a57..2acbf08a4c62e 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-07-03 +date: 2023-07-05 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 c57aa5a6b905f..12041897dce02 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-07-03 +date: 2023-07-05 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 5a5bafaad2647..488c71d92b97d 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-07-03 +date: 2023-07-05 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 f35498753f0dc..064cec1a5eed8 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-07-03 +date: 2023-07-05 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 20ba41af78b05..c583484c2cdf6 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-07-03 +date: 2023-07-05 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 72e523cd3bfcc..6f76e1d155eb8 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-07-03 +date: 2023-07-05 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 8bc58dcf9a0b1..c1f11e04df7a6 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-07-03 +date: 2023-07-05 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 49770b82a2317..4191ddb733757 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-07-03 +date: 2023-07-05 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 80a41f737d7a8..66f3a1bb801d3 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-07-03 +date: 2023-07-05 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 43feccb912076..2d5e5ff1e96a4 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-07-03 +date: 2023-07-05 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.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index 5d48630ad5476..250961d874a72 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -3104,19 +3104,19 @@ "section": "def-common.AppId", "text": "AppId" }, - " | \"serverlessElasticsearch\" | ", + " | ", { - "pluginId": "@kbn/deeplinks-observability", + "pluginId": "@kbn/deeplinks-management", "scope": "common", - "docId": "kibKbnDeeplinksObservabilityPluginApi", + "docId": "kibKbnDeeplinksManagementPluginApi", "section": "def-common.AppId", "text": "AppId" }, - " | ", + " | \"serverlessElasticsearch\" | ", { - "pluginId": "@kbn/deeplinks-management", + "pluginId": "@kbn/deeplinks-observability", "scope": "common", - "docId": "kibKbnDeeplinksManagementPluginApi", + "docId": "kibKbnDeeplinksObservabilityPluginApi", "section": "def-common.AppId", "text": "AppId" } diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index d18ed9db795cc..b3c10ca9a18b5 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-07-03 +date: 2023-07-05 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 3f904a46cf215..644b075c9c3b3 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-07-03 +date: 2023-07-05 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 36f2c3f8d4211..d69c3b7926792 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-07-03 +date: 2023-07-05 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 b13cbd06d06ae..24372e4bbdbcd 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-07-03 +date: 2023-07-05 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 07255a61ef8ee..2cb8f66c49c4c 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-07-03 +date: 2023-07-05 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 d43b547f61936..95e58fc0c9777 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-07-03 +date: 2023-07-05 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 574213ae5c3c5..d3dbd54ad9279 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-07-03 +date: 2023-07-05 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 d3f84a7278372..9b1e7a8745949 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-07-03 +date: 2023-07-05 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 ea12c1ad4746a..77edb606cd11a 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-07-03 +date: 2023-07-05 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 a40642519f373..0e196b2bd08ae 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-07-03 +date: 2023-07-05 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 f4e6020861c2c..8d8feab21d924 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-07-03 +date: 2023-07-05 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 4aad2c2eb3045..2c6385fe8c91e 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-07-03 +date: 2023-07-05 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 d6ea7c1ad0998..1bd4110d334f0 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-07-03 +date: 2023-07-05 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 c9f3ffa34e420..2c427d5efea09 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-07-03 +date: 2023-07-05 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 a1746de33122b..a1284fa96f99b 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-07-03 +date: 2023-07-05 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 79c322f80b0d5..be8722ffae792 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-07-03 +date: 2023-07-05 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 a816923aade61..f10a0ed1c2066 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-07-03 +date: 2023-07-05 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 1672f37050fde..64c3a41cadc0f 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-07-03 +date: 2023-07-05 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 9b4d14ae1b7cd..bf5b7f4a564db 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-07-03 +date: 2023-07-05 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 0565c0ab62ed2..4221cacdfd20c 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-07-03 +date: 2023-07-05 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 8389ce5249656..1c815d6ecedc1 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-07-03 +date: 2023-07-05 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 dd0d1aaad67dd..bb9f2e2927c07 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-07-03 +date: 2023-07-05 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 0f5abe010369c..ec38221f328ea 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-07-03 +date: 2023-07-05 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 5bfec1ca6c4b3..11d66e6acf880 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-07-03 +date: 2023-07-05 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 e7feab4db2118..7f8038073112b 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-07-03 +date: 2023-07-05 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 b78a3adba1397..3d3459d0d270f 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-07-03 +date: 2023-07-05 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 84154ebfde089..de0ccf46b3def 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-07-03 +date: 2023-07-05 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 e47082f7c1934..f11befc3b2955 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-07-03 +date: 2023-07-05 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 36e70d152ebfb..510a3c7ac009f 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-07-03 +date: 2023-07-05 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 80282eb1ff5fc..e8532585f92b9 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-07-03 +date: 2023-07-05 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 60d57dd5e3e38..26435a1ad1913 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-07-03 +date: 2023-07-05 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 3f958fd4c0e4a..d89a5b6b08229 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-07-03 +date: 2023-07-05 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 5f493fcce34cd..a5e9e261771f9 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-07-03 +date: 2023-07-05 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 d6aad01e676eb..c05397383fd58 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-07-03 +date: 2023-07-05 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 cd4b201905a62..a55127e548af9 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-07-03 +date: 2023-07-05 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 4eefbbc1a6212..c43348023f87c 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-07-03 +date: 2023-07-05 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 7202e3113edc7..44dd7858a0f8c 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-07-03 +date: 2023-07-05 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 35333e0495976..36b96d513bc87 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-07-03 +date: 2023-07-05 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 53697d0e06fa5..925fbf6464446 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-07-03 +date: 2023-07-05 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 99d48d422df00..f615bcb7abdaa 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-07-03 +date: 2023-07-05 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 fffc5966c181f..7da6ed3f8cbb9 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-07-03 +date: 2023-07-05 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 6b1abf77a61ad..ba262b7bb2b84 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-07-03 +date: 2023-07-05 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 e2d870af2bd90..10f606bdfd339 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-07-03 +date: 2023-07-05 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 558e64209ae58..22d4e70d3173e 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-07-03 +date: 2023-07-05 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 9302f4d7edfb9..19ca707814d45 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-07-03 +date: 2023-07-05 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 b49b267aa07d3..4c9af7f15ace0 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-07-03 +date: 2023-07-05 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 ff78a784afc47..2ba02ad258c15 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-07-03 +date: 2023-07-05 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 44e99bcd360f1..ffa0dbee6531d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index cdc4974ac790f..dfbbfb44b2ab6 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -3739,10 +3739,6 @@ "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/vulnerabilities_dashboard.ts" }, - { - "plugin": "cloudSecurityPosture", - "path": "x-pack/plugins/cloud_security_posture/server/routes/status/status.ts" - }, { "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts" @@ -14026,6 +14022,10 @@ "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts" }, + { + "plugin": "cloudSecurityPosture", + "path": "x-pack/plugins/cloud_security_posture/server/routes/status/status.ts" + }, { "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/csp_rule_template/get_csp_rule_template.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 9c8f8292e9203..0a7c4cebfbf5b 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-07-03 +date: 2023-07-05 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 fc90a923127ec..990cd120d005d 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-07-03 +date: 2023-07-05 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 fca10b2470a7b..0a32bbd81f22e 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-07-03 +date: 2023-07-05 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 da45a2b7a3937..5d486e605e5d9 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-07-03 +date: 2023-07-05 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 b16574938c496..53c2079403431 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-07-03 +date: 2023-07-05 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 e3e029d65d6d6..fd637cf169891 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-07-03 +date: 2023-07-05 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 6d281f60d24b6..663505c4f9d59 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-07-03 +date: 2023-07-05 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 df1e020523f62..7e54e17279400 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-07-03 +date: 2023-07-05 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 0ad4d0f2ec0ed..b61ff43c5b540 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-07-03 +date: 2023-07-05 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 9a81492fe1975..9dc6a88194b85 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-07-03 +date: 2023-07-05 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 3ec3daae38115..58fb313d243e2 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 212298f954c98..e903f08994ded 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-07-03 +date: 2023-07-05 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 626ce47187eac..fe7be83756d28 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-07-03 +date: 2023-07-05 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 e2830ae5cf016..3addbfc94941f 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-07-03 +date: 2023-07-05 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 64e32ba8edc67..1e4aca3b887cf 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-07-03 +date: 2023-07-05 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 e461b3fb20f9a..eed6ea4e108d5 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-07-03 +date: 2023-07-05 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 34b1a1178c5d7..63e02d42af553 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-07-03 +date: 2023-07-05 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 f3236e8121fdd..0eba98df374b5 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-07-03 +date: 2023-07-05 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 73adce21cdcf7..4da2a009012cc 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-07-03 +date: 2023-07-05 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 d28da30ecdd5d..1b2a36ee8af4c 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-07-03 +date: 2023-07-05 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 96963764a6763..12aa4833fa682 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-07-03 +date: 2023-07-05 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 8c75dde632791..f0ddb326d4b89 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-07-03 +date: 2023-07-05 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 52153d6ff57da..5615047cd64a6 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-07-03 +date: 2023-07-05 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 00a6e9c8ad6d0..0b0bb11d0bfc5 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-07-03 +date: 2023-07-05 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 a4c20122cb118..0d0f7a04f1de5 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-07-03 +date: 2023-07-05 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 7f21472bb92e0..5f038e86d92dc 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 909233cc5ab6f..807bab424b2d7 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index bef05ac9357c3..0049ea05bae51 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 8d3df740e56ff..ef0ed68bd3577 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-07-03 +date: 2023-07-05 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 bb98f55ea0c41..29fff33334979 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-07-03 +date: 2023-07-05 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 8e6d151c4d659..35e8dc6b2ba2f 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-07-03 +date: 2023-07-05 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 313654df45a85..f8a3e9a121e1f 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-07-03 +date: 2023-07-05 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 772ceeee94c36..3f3fe477b5a01 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-07-03 +date: 2023-07-05 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 50f5127ccc679..53dcf1ad9aac0 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-07-03 +date: 2023-07-05 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 efe823a173a12..c6b969274388a 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-07-03 +date: 2023-07-05 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 d9e879a748dc3..516617eea9696 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-07-03 +date: 2023-07-05 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 602278778c3e6..12ee8f08e3411 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-07-03 +date: 2023-07-05 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 15fadabe771b3..576d45988c503 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-07-03 +date: 2023-07-05 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 ecd45f7308de0..cbae006cbe3f5 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-07-03 +date: 2023-07-05 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 8ce163e266509..347a157cd85ba 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-07-03 +date: 2023-07-05 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 054389789e925..2ed786d7e5d5d 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-07-03 +date: 2023-07-05 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 18d62db4cc786..a91692e09d9fd 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-07-03 +date: 2023-07-05 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 f2bff07a80180..73bb0667dc436 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-07-03 +date: 2023-07-05 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 f55cdab569d6b..6e914a5a786eb 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 7bcc25af5e680..93332ea1f401b 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index dc2c8b47d9168..0d5927b0ee018 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-07-03 +date: 2023-07-05 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 678967576150b..b83e74136bc2e 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-07-03 +date: 2023-07-05 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_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index fa67962a4b569..928b33d7d50e4 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 9dffcf9fef3f8..d9fc5e6bbea9e 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-07-03 +date: 2023-07-05 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 6b9dbfe06b4c3..4902d8d31c73a 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-07-03 +date: 2023-07-05 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 ee12e8cc98ddb..bafd0df0fdce0 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-07-03 +date: 2023-07-05 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 f6fdabf1d6567..92665b7bcb74c 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-07-03 +date: 2023-07-05 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 3e05cafa5ddd4..b01436ed5928a 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index 7b4695420afa3..fbc383d88c59c 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -1489,34 +1489,6 @@ "plugin": "data", "path": "src/plugins/data/server/search/saved_objects/search_session_migration.test.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 149fbedfff6a9..893ac78382639 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-07-03 +date: 2023-07-05 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 ea4258f65d0d3..ad40ac69d5eba 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-07-03 +date: 2023-07-05 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 a037d82b40549..0be9a0e8206d2 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-07-03 +date: 2023-07-05 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.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index f50bbb8616046..8371af4f13cd8 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index a478edb319751..663dc071f28f2 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-07-03 +date: 2023-07-05 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 35596d4d833c9..24e76ceb5f53a 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-07-03 +date: 2023-07-05 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 b0b5255227a36..e945f00f884ae 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-07-03 +date: 2023-07-05 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 7a0b0f68d5d32..168156da1ddb9 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-07-03 +date: 2023-07-05 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 8d295836a6cf8..1c5232d33b214 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-07-03 +date: 2023-07-05 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 768599b5053bb..98b0249f8e054 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-07-03 +date: 2023-07-05 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 511a84babbe8f..2dbdc902c77e2 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-07-03 +date: 2023-07-05 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 2fc2797f45d10..0650139c640af 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-07-03 +date: 2023-07-05 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 37cc2281c42be..a1dccd271d397 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-07-03 +date: 2023-07-05 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 baf2f79e04953..b0abaa8abedf4 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-07-03 +date: 2023-07-05 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 92205214d635c..1940f37349838 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-07-03 +date: 2023-07-05 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 beb474f595f4f..bd42702bb7617 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-07-03 +date: 2023-07-05 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 b6e914193798e..475de1f36a366 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-07-03 +date: 2023-07-05 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 7dbe9a90426e3..4f91385037bca 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-07-03 +date: 2023-07-05 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 0d6ecf679c5b0..96534af7d163a 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-07-03 +date: 2023-07-05 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 0e6f51e1c3309..2f2122a4c41cc 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-07-03 +date: 2023-07-05 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 142f6035898c2..f3596e6c9b442 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-07-03 +date: 2023-07-05 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 f7d9767b813e1..7a72019e2a24c 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-07-03 +date: 2023-07-05 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 e39273566fa65..9323f3c818db8 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-07-03 +date: 2023-07-05 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 5057dd29a20de..8b3058b3c42b4 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-07-03 +date: 2023-07-05 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 ee79b6d1f903b..d39b2db40ed50 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-07-03 +date: 2023-07-05 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 f9b62b1dc92ce..938efa0a423a7 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-07-03 +date: 2023-07-05 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 bec01eeeb7033..416334f6351b9 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-07-03 +date: 2023-07-05 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 cffe5df4a856a..2283b43fc865b 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-07-03 +date: 2023-07-05 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 9bb9dc64916f2..f6852008c9996 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-07-03 +date: 2023-07-05 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 b5e3493f82751..b6967380d8820 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-07-03 +date: 2023-07-05 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 0ca51dd533610..bd939750029ce 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-07-03 +date: 2023-07-05 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 d8aee8bf1a7f9..f9412de052cf4 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-07-03 +date: 2023-07-05 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_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 39a86e004ef48..a3cd79cb0f2a2 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index f851a352f922d..193e7472a6022 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 81e97a47315f8..657bd8444eef1 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 83d77c684910f..8116a3f0da0ff 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-07-03 +date: 2023-07-05 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 e39edeea0c06c..50c1bc8a38b2e 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-07-03 +date: 2023-07-05 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 5bb1ef01346fe..b4e7d659b4b7f 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 7d2b146ae217b..ccf31fc5b622f 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index e39f50ec0a177..631882a8f5a2b 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 8e46553951fd4..6431eb586768a 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index b12bd06725302..a0f5ab936f828 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.devdocs.json b/api_docs/kbn_deeplinks_management.devdocs.json index 9132a4b8b3809..4d6cf71eb42eb 100644 --- a/api_docs/kbn_deeplinks_management.devdocs.json +++ b/api_docs/kbn_deeplinks_management.devdocs.json @@ -45,9 +45,7 @@ "label": "DeepLinkId", "description": [], "signature": [ - "IntegrationsDeepLinkId", - " | ", - "ManagementDeepLinkId" + "\"fleet\" | \"monitoring\" | \"management\" | \"integrations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:settings\" | \"management:dataViews\" | \"management:spaces\" | \"management:reporting\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:api_keys\" | \"management:cross_cluster_replication\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\"" ], "path": "packages/deeplinks/management/deep_links.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index d8de57ea9eb71..cf43a8b6cdd8c 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/platform-deployment-management](https://github.com/orgs/elasti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 4 | 0 | 4 | 2 | +| 4 | 0 | 4 | 0 | ## Common diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 3970bdf914e05..0517bf418e899 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 24d30e69b89e7..3e428f46cae50 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 5ee03ee5870e7..edb11e1ef6bbd 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index d5b977c5cdee8..81d527ce01372 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index b9c31f0d9e983..d1c9856de5f31 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 7a6ec8b0b2418..a65dba1592e6a 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 2a63690175ac1..c4f24d090c74d 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 0c5e55f1530a3..1510d767c6bf8 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-07-03 +date: 2023-07-05 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 bc8cfc1ec87ad..d28350f75cb2f 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-07-03 +date: 2023-07-05 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 2ccea747389a6..1bf6c89dfe937 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-07-03 +date: 2023-07-05 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 c57740608bdd1..c95a7feb12ecc 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 85fbfcea2892d..33436d8bacdca 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -658,7 +658,7 @@ "label": "observability", "description": [], "signature": [ - "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly threshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsCommandReference: string; readonly syntheticsProjectMonitors: string; readonly syntheticsMigrateFromIntegration: string; }" + "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly threshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsCommandReference: string; readonly syntheticsProjectMonitors: string; readonly syntheticsMigrateFromIntegration: string; readonly sloBurnRateRule: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index b5e4d8524be67..ae81cabcc0fd8 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-07-03 +date: 2023-07-05 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 73419b45a76af..d72e51b1ab038 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 4b6d0775c4b0e..2a2740d570c36 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 2eb3f2d9143d3..1c35244834411 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-07-03 +date: 2023-07-05 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 c7a1891389411..0f8efd19b9a29 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-07-03 +date: 2023-07-05 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 6a512056beced..12b55452a3798 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-07-03 +date: 2023-07-05 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_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 2f2101d15abd1..d7aa33323d970 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 1239f4f1c5e00..f817ec0859dc1 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-07-03 +date: 2023-07-05 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 37873c856d57f..d5c755b334ed8 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-07-03 +date: 2023-07-05 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 78131a2926647..5718752bd3dd4 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-07-03 +date: 2023-07-05 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 6c30bc36e3c11..d59bb5ddd951d 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-07-03 +date: 2023-07-05 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 596300c1b5913..2081f038f43e4 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-07-03 +date: 2023-07-05 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 570b75b924064..fccc76a9972c2 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index 6cbe0e1c4e47c..5e87a772ddaa4 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index dca9f7d611e9c..c76e5be48d94d 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-07-03 +date: 2023-07-05 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 762218ef04c43..cc927d2563282 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-07-03 +date: 2023-07-05 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 ee52ceb039d68..64285e4ea52d3 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-07-03 +date: 2023-07-05 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 614a3fc288bb2..2a4252112f6a7 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index f5948cfc5773d..d450935d31331 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 67f03a9e45bc8..bf78b04382785 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 797b415e2da5f..0c18490fc9c0a 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 0d95afc4efd7f..df7d4da812877 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 60231ea44d306..76e2fcedb8f6b 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-07-03 +date: 2023-07-05 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 71a44612532b0..707eb01fdfdd9 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-07-03 +date: 2023-07-05 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 a19ad26316f30..41f99fbe22ff6 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-07-03 +date: 2023-07-05 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 a569f10bf7bd9..12b114d11419d 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-07-03 +date: 2023-07-05 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 6daa232d8b9b0..40954ee038b46 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-07-03 +date: 2023-07-05 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 347792b5678fa..1e046a3e074ee 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-07-03 +date: 2023-07-05 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 bb21d9c01981e..a09a6acb7966a 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-07-03 +date: 2023-07-05 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 65ff9f5173629..3aa45aea29ce6 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index a7c683a49990d..69afe9c8be9d7 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 77cd989c02842..3148ef143f500 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-07-03 +date: 2023-07-05 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 d2fa70c64ec43..2522ef4484934 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-07-03 +date: 2023-07-05 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 38fdd3c2e61d6..fc145dbadb6c1 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-07-03 +date: 2023-07-05 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 d687e871dc698..875b50dca5058 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-07-03 +date: 2023-07-05 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 e2c176c38744e..2882e4a85d646 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-07-03 +date: 2023-07-05 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 b8bd537cdf4b1..7e3446d54e950 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-07-03 +date: 2023-07-05 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 eef98288dea02..f0b0dee2e59a1 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-07-03 +date: 2023-07-05 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 150f581c08744..d8656d2c7b08e 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-07-03 +date: 2023-07-05 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 f9231eb985b6c..b874ca581b2ed 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-07-03 +date: 2023-07-05 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 f035ae091252d..6427e57a56090 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 1031ad4ffaa31..4801a3d431dd7 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 6be3e936cde50..b36c548b3a256 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 7c38ff1a3d33b..1acfc03f758ed 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 4441d8cfe1704..97ce255d43aff 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 257ba4e7fc4e5..4b86e5a5d91da 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-07-03 +date: 2023-07-05 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_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index bd1e1501481cd..81b7732d7d54d 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index 67992c1e25073..67e7bebfd62ce 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index 9639238308b58..eb2bda204fb60 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 8ae9d959e5c41..06cc579d58af8 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-07-03 +date: 2023-07-05 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_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index d6a6621ffa99c..c79d46c85e15c 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index f73cb8fb9678f..83f67640a203f 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 4a3a0ec674c29..8a7723e9f4b52 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-07-03 +date: 2023-07-05 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 6a39f28206866..32266a258893e 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-07-03 +date: 2023-07-05 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_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index e074cff2362f1..902e61e596883 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index c612dde0333f7..d83547a5193d6 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-07-03 +date: 2023-07-05 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 0588bf88fca63..c0bc51865d04d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 6efe0f92bf669..9bae59a6eacf9 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 4c9d14a51e53a..2b05f974f8297 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 1b34b0a238b16..2f37f244dacfd 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 42d7a07ebc6d5..f4cffc6552377 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index a16555eb88815..deb6851a6694d 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 22e457a4ebcf2..53c42072a23a0 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 2ec62c0170c42..667e781b31b2c 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 85b4f63b897b4..81b827e7d47b9 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-07-03 +date: 2023-07-05 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 f302e95a34e30..b677020dbfd90 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 1d18012d3d06e..4c8b8c779ad23 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index ab0be9166607a..724aeb08bced6 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index dcfab0f732e8d..fe46d23c82bf0 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-07-03 +date: 2023-07-05 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 8de49e90a1219..0ee301007ce39 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-07-03 +date: 2023-07-05 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 e96708f0d7dee..3de2f47c2a484 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-07-03 +date: 2023-07-05 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 a698699823a0b..c0a39fff7e0d9 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-07-03 +date: 2023-07-05 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 6d31f2d49b399..35dadb27af453 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-07-03 +date: 2023-07-05 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 8f18fcc306e78..f825c8a465ca3 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index cb2c76074f3d9..b5fbc1b7c5f3f 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index fbc5385de83c6..0b08ea7363967 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-07-03 +date: 2023-07-05 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 b10707fad90a5..554d4dcb5af84 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-07-03 +date: 2023-07-05 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 1a5fba09feb55..955ef83ff1cdc 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-07-03 +date: 2023-07-05 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 a9e4810644cab..2c9945dbba730 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-07-03 +date: 2023-07-05 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 1a20fb5676183..550a8eaf50a67 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index d9b27d4ae9f9c..43c1420846cef 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index f4386f74487ed..e259a39092f0c 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 7fe59eb4f5553..6d36cbbebda1e 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 6e4d33e4c9fd9..156c11bb3eb1e 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index b259e39c97e00..fea62bc327b5f 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 3c3c34c2c8d23..04021ce24ff92 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index ae75ed838a5fb..00ca6215b5697 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index c469670905c4e..1ed0c4cc1bb23 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 239a7da01e2ba..474af441e362e 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index bdcac24385621..b0054b96656be 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-07-03 +date: 2023-07-05 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 286bafc4be8ae..062ff3f7fd368 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-07-03 +date: 2023-07-05 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 ced451bf44cf8..a43b54c7e8be6 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index 134ded8959412..765aca6e6ccb2 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 0f5e65cdca82d..01b91c96f5fb8 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-07-03 +date: 2023-07-05 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 243de472d8d16..c6930ecbabaca 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-07-03 +date: 2023-07-05 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 20c322ba72341..a49d0cf0ea181 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-07-03 +date: 2023-07-05 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 86f58857a5b88..cc151193b5f4d 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-07-03 +date: 2023-07-05 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 fe7aafdde510a..f262893b0d38e 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-07-03 +date: 2023-07-05 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 9351ba6a82274..9734f0e0acad9 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-07-03 +date: 2023-07-05 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 bed2d7ceb892a..4e736d97d3972 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-07-03 +date: 2023-07-05 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 8864c0556d09c..5dbb710e0fc0c 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-07-03 +date: 2023-07-05 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 b771b4233129c..64601b6d4c4c9 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-07-03 +date: 2023-07-05 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 48dd017d21cd8..6393c27aa5b16 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-07-03 +date: 2023-07-05 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 e017156293a51..8ae592064a6ff 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-07-03 +date: 2023-07-05 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 ccd098e0cce09..25d56bed1306c 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-07-03 +date: 2023-07-05 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 b4937bf6ce69e..642c8490fc15b 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-07-03 +date: 2023-07-05 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 60bf1fc183a50..09988c969aed2 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index eb5254b30807c..66f94916d8283 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 3050200b77926..9ff861f91a90d 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 43a79bb804df9..b30b411128fa2 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-07-03 +date: 2023-07-05 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 365d382795851..1c1210832eaa1 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-07-03 +date: 2023-07-05 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 e3f3bc58ab2e3..531bc47c943a8 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-07-03 +date: 2023-07-05 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 bbf239c1a54f5..2c912c64b30cf 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-07-03 +date: 2023-07-05 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 c2ffcbecd4cbf..3c07ddc4652cf 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-07-03 +date: 2023-07-05 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 0adfb9feb1200..bd70bd98c3692 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-07-03 +date: 2023-07-05 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 4318ee625009d..706bac4f59acd 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-07-03 +date: 2023-07-05 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 81ce041216314..02102583a77b0 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-07-03 +date: 2023-07-05 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_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index c4513f0950273..ac8e0150cad76 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 8822612effecf..8e3c4ff5c8ec3 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index b8e314847b7d2..ea250c4c3a7fb 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-07-03 +date: 2023-07-05 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 54002fd5e231a..07b09f5f34f91 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 4833a17b13646..1d51faf21ef34 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 69bf1ba9714c0..b263a80e5f5d0 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 603f55f21452c..03137d11e0274 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 9d35e93e55d58..3d18c0daf5461 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index aea6d9ca9c944..6b4a7568839ba 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-07-03 +date: 2023-07-05 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 73a24de7c582e..b255f6230f787 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-07-03 +date: 2023-07-05 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 8d4e9f253a740..27f167249ea83 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-07-03 +date: 2023-07-05 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 3259792fa8a3e..bc4473a08f23f 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-07-03 +date: 2023-07-05 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 47995a01be940..886cb45856c00 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-07-03 +date: 2023-07-05 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 94ce217ad5251..811fc7c95e689 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-07-03 +date: 2023-07-05 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 b4208bdb934eb..3b0e8c63569a6 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-07-03 +date: 2023-07-05 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 a60e61262df8a..fd74062daae01 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-07-03 +date: 2023-07-05 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 c34c48ba5a2c4..91379e848e63e 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-07-03 +date: 2023-07-05 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 5625062922f68..59ef177721ddf 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-07-03 +date: 2023-07-05 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 ae72e8f25ece2..0835f6dab888c 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-07-03 +date: 2023-07-05 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 cd7f501ef59bd..63b6cc21a36f8 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-07-03 +date: 2023-07-05 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 fb7deaf5ef3df..c7255c2077499 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-07-03 +date: 2023-07-05 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 bf6a8f93ab49e..e218153d7da31 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-07-03 +date: 2023-07-05 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 43cfd25a4b3a9..1a536d3315703 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-07-03 +date: 2023-07-05 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 176160b9a9613..3e70d4567cc79 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-07-03 +date: 2023-07-05 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 7ec40f853f623..8a598bc22cf94 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-07-03 +date: 2023-07-05 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 7606162f3db6b..75b73b8a877b3 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-07-03 +date: 2023-07-05 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 926fcbffc93e8..8c72280b0a6d1 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-07-03 +date: 2023-07-05 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 e0e6eb404306d..987c7d0586ca7 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-07-03 +date: 2023-07-05 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 8ab5f358d30b9..c8b99990edbe9 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-07-03 +date: 2023-07-05 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 c40f63fd15ba6..e4fbe5a4291ad 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-07-03 +date: 2023-07-05 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 9c12c13d09d0e..ed436ac1cd0f0 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-07-03 +date: 2023-07-05 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 b6b5e86ea67e0..e106b8562a601 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-07-03 +date: 2023-07-05 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 5bde2ad8365f7..ae23535f6b45f 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-07-03 +date: 2023-07-05 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 66f26519d1ab6..67ff55eefa97d 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-07-03 +date: 2023-07-05 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 815a97c1dfffe..048c6f24c03b6 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-07-03 +date: 2023-07-05 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 9507e46669d50..434f426e02a46 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-07-03 +date: 2023-07-05 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 96b4e7cb42636..024c14d8b5033 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-07-03 +date: 2023-07-05 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 e10964750f681..6db2677eba7ae 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-07-03 +date: 2023-07-05 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 16e559f3cd948..eb3509a798cda 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-07-03 +date: 2023-07-05 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 5386812542a2d..1009301eb174f 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-07-03 +date: 2023-07-05 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 d8c75ae07419a..39bf68611d8d2 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index f37190ddc1e0f..8923c6e8831e6 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 101042ea6c065..22cb1362d684a 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-07-03 +date: 2023-07-05 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 b4fcaea9aecd0..e4af602a22d07 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-07-03 +date: 2023-07-05 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 0bf8cb2d6e039..12fc03dc73aaa 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-07-03 +date: 2023-07-05 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 23560795684df..21132d0b49eff 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-07-03 +date: 2023-07-05 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 07cd8dbb085ff..4b91f5241febd 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-07-03 +date: 2023-07-05 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 c445dd46160a8..4f49eeac558e5 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 5fbabd339666d..60361fd6e2243 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index e8551189096d5..3828f42063e9d 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index fde8c7f3edeb7..6439073eac901 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-07-03 +date: 2023-07-05 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 d911e61e165d3..ad7fcc1d9a86e 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-07-03 +date: 2023-07-05 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 d322837209b81..1d3ad06f90eec 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-07-03 +date: 2023-07-05 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 d48c3e8f348b1..8d09226d8cde8 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-07-03 +date: 2023-07-05 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 d9e75497bad3f..085cecb17cd01 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-07-03 +date: 2023-07-05 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 ee7ba8f9abebf..6940bb92bfde0 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-07-03 +date: 2023-07-05 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 c5ff29da175ea..88e2b49ec80e6 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-07-03 +date: 2023-07-05 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 28a0faf0b7a93..fc785b8740ab7 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-07-03 +date: 2023-07-05 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 ac0766b2756c1..86f3e74f10c56 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index e151c4ab1fe45..54a301cad20ec 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -3183,6 +3183,28 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "lens", + "id": "def-public.LensPublicStart.EditLensConfigPanelApi", + "type": "Function", + "tags": [ + "experimental" + ], + "label": "EditLensConfigPanelApi", + "description": [ + "\nReact component which can be used to embed a Lens Visualization Config Panel Component.\n\nThis API might undergo breaking changes even in minor versions.\n" + ], + "signature": [ + "() => Promise<", + "EditLensConfigPanelComponent", + ">" + ], + "path": "x-pack/plugins/lens/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.LensPublicStart.navigateToPrefilledEditor", @@ -5884,6 +5906,55 @@ ], "returnComment": [] }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getCustomRemoveLayerText", + "type": "Function", + "tags": [], + "label": "getCustomRemoveLayerText", + "description": [ + "\nThis method is a clunky solution to the problem, but I'm banking on the confirm modal being removed\nwith undo/redo anyways" + ], + "signature": [ + "((layerId: string, state: T) => { title?: string | undefined; description?: string | undefined; } | undefined) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getCustomRemoveLayerText.$1", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getCustomRemoveLayerText.$2", + "type": "Uncategorized", + "tags": [], + "label": "state", + "description": [], + "signature": [ + "T" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.Visualization.getLayerType", diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 0a1c3bae11104..67b3a0c09834d 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 622 | 0 | 527 | 58 | +| 626 | 0 | 529 | 59 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 9225b262dbe9f..418c7d6471634 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-07-03 +date: 2023-07-05 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 8de872753892a..f78f30f5ea87a 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-07-03 +date: 2023-07-05 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 047070d494b97..57ed54ac6f483 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-07-03 +date: 2023-07-05 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 bbaaad6c45db5..05c5960070511 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.devdocs.json b/api_docs/management.devdocs.json index 6e970de9f2c57..61f282b25ee2c 100644 --- a/api_docs/management.devdocs.json +++ b/api_docs/management.devdocs.json @@ -805,6 +805,38 @@ ], "returnComment": [] }, + { + "parentPluginId": "management", + "id": "def-public.ManagementStart.setLandingPageRedirect", + "type": "Function", + "tags": [], + "label": "setLandingPageRedirect", + "description": [], + "signature": [ + "(landingPageRedirect: string) => void" + ], + "path": "src/plugins/management/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "management", + "id": "def-public.ManagementStart.setLandingPageRedirect.$1", + "type": "string", + "tags": [], + "label": "landingPageRedirect", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/management/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "management", "id": "def-public.ManagementStart.setupCardsNavigation", diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 6f730945cc4ad..b7d9c0485d960 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/platform-deployment-management](https://github.com/orgs/elasti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 45 | 0 | 45 | 7 | +| 47 | 0 | 47 | 7 | ## Client diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 7dce59669f358..dbc775b5f1dbc 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-07-03 +date: 2023-07-05 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 254967debb785..b5d807b56d5a4 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-07-03 +date: 2023-07-05 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 a7c468d0acbdf..f58609b971172 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-07-03 +date: 2023-07-05 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 ca697dc1cc9ec..41e3baa9bd414 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-07-03 +date: 2023-07-05 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 f7df9e858e768..bd1a9da908500 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-07-03 +date: 2023-07-05 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 40c69346b9445..10e65aecf96a6 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-07-03 +date: 2023-07-05 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 a381c4195c2d9..a6c451b483497 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-07-03 +date: 2023-07-05 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 72fe7e558062f..88f460f7ef274 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 19b04cca8047c..71425278a3406 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 7cccdcb4a8c98..2ccee1c31e89c 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 45d01be1c6443..0c300be82aaf0 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 0c3e65f60d78a..49af78d45b357 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-07-03 +date: 2023-07-05 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 713bd0c62c1e2..a847b1da1639f 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 70960 | 544 | 60773 | 1410 | +| 70987 | 544 | 60808 | 1409 | ## Plugin Directory @@ -56,11 +56,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 271 | 0 | 252 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 100 | 0 | 98 | 9 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 54 | 0 | 51 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3303 | 119 | 2579 | 27 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3303 | 119 | 2583 | 27 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1048 | 0 | 258 | 2 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1051 | 0 | 268 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 10 | 0 | 8 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 71 | 14 | @@ -73,7 +73,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 10 | 0 | 10 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 115 | 3 | 111 | 3 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 234 | 30 | 234 | 4 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 236 | 30 | 236 | 4 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 116 | 0 | 116 | 11 | | | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 141 | 1 | 141 | 14 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | @@ -117,13 +117,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 609 | 3 | 416 | 9 | | | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | - | 5 | 0 | 5 | 1 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 622 | 0 | 527 | 58 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 626 | 0 | 529 | 59 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 210 | 0 | 94 | 51 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | -| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 45 | 7 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 47 | 0 | 47 | 7 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 266 | 0 | 265 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 150 | 3 | 64 | 32 | @@ -155,7 +155,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 5 | | searchprofiler | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 283 | 0 | 94 | 1 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 159 | 2 | 115 | 30 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 174 | 2 | 130 | 30 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 17 | 0 | 16 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Serverless customizations for observability. | 6 | 0 | 6 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Serverless customizations for search. | 6 | 0 | 6 | 0 | @@ -180,7 +180,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 547 | 11 | 521 | 50 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 144 | 2 | 102 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 206 | 0 | 140 | 9 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 52 | 0 | 23 | 2 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 53 | 0 | 23 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 137 | 2 | 100 | 20 | | upgradeAssistant | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | uptime | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | @@ -410,7 +410,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 43 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 5 | 0 | 5 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 5 | 0 | 5 | 0 | -| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 2 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 3 | 0 | 3 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 4 | 0 | 4 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 18f78a4ea2427..c03517c32181d 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-07-03 +date: 2023-07-05 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 ae042466b51ee..d38e239182911 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-07-03 +date: 2023-07-05 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 cdf5e69b2a7bf..0dbe8450665c2 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-07-03 +date: 2023-07-05 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 bde1754b6d36b..66305771e23fc 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/reporting_export_types.mdx b/api_docs/reporting_export_types.mdx index 666e74dc24ee1..6ec11afb27a39 100644 --- a/api_docs/reporting_export_types.mdx +++ b/api_docs/reporting_export_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reportingExportTypes title: "reportingExportTypes" image: https://source.unsplash.com/400x175/?github description: API docs for the reportingExportTypes plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reportingExportTypes'] --- import reportingExportTypesObj from './reporting_export_types.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 56ef3a6a083b1..6df0719478949 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 0e2f5fa7c3ca2..01fee08efd10a 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-07-03 +date: 2023-07-05 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 4b755988ed70f..ff1c503e48f9c 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 745cc7089a5f7..6bf8190ccf2a3 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 24f360208256f..773ef8d73391c 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-07-03 +date: 2023-07-05 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 31ca9217da8fa..0cbb23923e783 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-07-03 +date: 2023-07-05 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 e621382187582..90d1b3092d057 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-07-03 +date: 2023-07-05 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 ca16449b6e8bd..922893523ded1 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-07-03 +date: 2023-07-05 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 ddd11a2636676..7dd50c518a8ba 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-07-03 +date: 2023-07-05 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 f273270e2ac70..7507e2a9f95dd 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-07-03 +date: 2023-07-05 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 eeecae3ee29db..e74025234fa80 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-07-03 +date: 2023-07-05 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 57a720991cc39..5057f57deedee 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-07-03 +date: 2023-07-05 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 226389e31e6b9..072b0392348c3 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -634,6 +634,230 @@ ], "functions": [], "interfaces": [ + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink", + "type": "Interface", + "tags": [], + "label": "NavigationLink", + "description": [], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.categories", + "type": "Object", + "tags": [], + "label": "categories", + "description": [], + "signature": [ + "readonly ", + { + "pluginId": "@kbn/security-solution-side-nav", + "scope": "common", + "docId": "kibKbnSecuritySolutionSideNavPluginApi", + "section": "def-common.LinkCategory", + "text": "LinkCategory" + }, + "<", + { + "pluginId": "securitySolution", + "scope": "common", + "docId": "kibSecuritySolutionPluginApi", + "section": "def-common.SecurityPageName", + "text": "SecurityPageName" + }, + ">[] | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.disabled", + "type": "CompoundType", + "tags": [], + "label": "disabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.id", + "type": "Enum", + "tags": [], + "label": "id", + "description": [], + "signature": [ + { + "pluginId": "securitySolution", + "scope": "common", + "docId": "kibSecuritySolutionPluginApi", + "section": "def-common.SecurityPageName", + "text": "SecurityPageName" + } + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.landingIcon", + "type": "CompoundType", + "tags": [], + "label": "landingIcon", + "description": [], + "signature": [ + "IconType", + " | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.landingImage", + "type": "string", + "tags": [], + "label": "landingImage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.links", + "type": "Array", + "tags": [], + "label": "links", + "description": [], + "signature": [ + { + "pluginId": "securitySolution", + "scope": "public", + "docId": "kibSecuritySolutionPluginApi", + "section": "def-public.NavigationLink", + "text": "NavigationLink" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.sideNavIcon", + "type": "CompoundType", + "tags": [], + "label": "sideNavIcon", + "description": [], + "signature": [ + "IconType", + " | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.skipUrlState", + "type": "CompoundType", + "tags": [], + "label": "skipUrlState", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.unauthorized", + "type": "CompoundType", + "tags": [], + "label": "unauthorized", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.isBeta", + "type": "CompoundType", + "tags": [], + "label": "isBeta", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.NavigationLink.betaOptions", + "type": "Object", + "tags": [], + "label": "betaOptions", + "description": [], + "signature": [ + "{ text: string; } | undefined" + ], + "path": "x-pack/plugins/security_solution/public/common/links/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "securitySolution", "id": "def-public.TimelineModel", @@ -1679,7 +1903,13 @@ "() => ", "Observable", "<", - "NavigationLink", + { + "pluginId": "securitySolution", + "scope": "public", + "docId": "kibSecuritySolutionPluginApi", + "section": "def-public.NavigationLink", + "text": "NavigationLink" + }, "[]>" ], "path": "x-pack/plugins/security_solution/public/types.ts", @@ -1751,6 +1981,26 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.PluginStart.getBreadcrumbsNav$", + "type": "Function", + "tags": [], + "label": "getBreadcrumbsNav$", + "description": [], + "signature": [ + "() => ", + "Observable", + "<", + "BreadcrumbsNav", + ">" + ], + "path": "x-pack/plugins/security_solution/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "lifecycle": "start", diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 5a543aeb6ccf5..f49c4e732e19a 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 159 | 2 | 115 | 30 | +| 174 | 2 | 130 | 30 | ## Client diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 2442708db81a7..b378e96dd1769 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 3c8c535fcfdf6..086f5ddd62ae3 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index f9592d2e85309..0e2906e0c48f7 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/serverless_security.mdx b/api_docs/serverless_security.mdx index f3c01d3ce6021..d3f04f77aa789 100644 --- a/api_docs/serverless_security.mdx +++ b/api_docs/serverless_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSecurity title: "serverlessSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSecurity plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSecurity'] --- import serverlessSecurityObj from './serverless_security.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index d2fde45c2ea69..9996b614d4f55 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-07-03 +date: 2023-07-05 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 7e960f2472051..6fee690d24ee7 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-07-03 +date: 2023-07-05 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 a0b069f790b58..b6414973bfac4 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-07-03 +date: 2023-07-05 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 6bd068ccfb441..69d2bb90c629a 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-07-03 +date: 2023-07-05 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 871a33484bd3d..3c4331805d1bc 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-07-03 +date: 2023-07-05 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 26dd11a63e519..ee04c17c8a08c 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-07-03 +date: 2023-07-05 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 06ea30b7ee13a..5cb5c232a8c2e 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index cbc4dcc929706..46d841e2ff7f1 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-07-03 +date: 2023-07-05 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 a175d612a4e7b..bfd9aa4afee77 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-07-03 +date: 2023-07-05 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 18a82f1b71edf..e4b7f79a9375e 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-07-03 +date: 2023-07-05 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 79a860406a302..5c2e12eebba7a 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 50dc9a5316727..5888c1b29a889 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 34394247d19ce..007d55d57e774 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-07-03 +date: 2023-07-05 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 b0de367357e96..41fa43757d384 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-07-03 +date: 2023-07-05 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 e7f9632f00861..d913ae767c551 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index cbcb2011869cf..04571ec89b674 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-07-03 +date: 2023-07-05 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 cad6c4aca0a0d..1187fdb94ea9a 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-07-03 +date: 2023-07-05 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 2756ee9783a4f..cd1ab232e8d84 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_histogram.devdocs.json b/api_docs/unified_histogram.devdocs.json index 39768585d6aa5..5b5b6d13ef809 100644 --- a/api_docs/unified_histogram.devdocs.json +++ b/api_docs/unified_histogram.devdocs.json @@ -468,7 +468,7 @@ }, " | undefined; } & Pick<", "UnifiedHistogramLayoutProps", - ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\"> & React.RefAttributes<", + ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"timeRange\" | \"dataView\" | \"services\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\"> & React.RefAttributes<", { "pluginId": "unifiedHistogram", "scope": "public", @@ -892,6 +892,30 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedHistogram", + "id": "def-public.UnifiedHistogramState.lensTablesAdapter", + "type": "Object", + "tags": [], + "label": "lensTablesAdapter", + "description": [ + "\nThe current Lens request table" + ], + "signature": [ + "Record | undefined" + ], + "path": "src/plugins/unified_histogram/public/container/services/state_service.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedHistogram", "id": "def-public.UnifiedHistogramState.timeInterval", @@ -1154,7 +1178,7 @@ }, " | undefined; } & Pick<", "UnifiedHistogramLayoutProps", - ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\">" + ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"timeRange\" | \"dataView\" | \"services\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\">" ], "path": "src/plugins/unified_histogram/public/container/container.tsx", "deprecated": false, diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index a937258c3abb8..0197cf908f0e4 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 52 | 0 | 23 | 2 | +| 53 | 0 | 23 | 2 | ## Client diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 0fee1d0bb852a..9a44bd9bc3470 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-07-03 +date: 2023-07-05 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 4c7e6ec585f92..229cf50806970 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-07-03 +date: 2023-07-05 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 52ea956af6173..c5232b0b57c25 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-07-03 +date: 2023-07-05 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 28fda03e4c7b0..1a6e7e8e985ee 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-07-03 +date: 2023-07-05 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 35e4985760225..3c3b9dd888195 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-07-03 +date: 2023-07-05 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 c46d34717bc97..bc101a54eee9c 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-07-03 +date: 2023-07-05 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 963e602c35be5..9b98623cf9ef6 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-07-03 +date: 2023-07-05 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 23040c12a4a17..7be1137b65531 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-07-03 +date: 2023-07-05 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 3ab7c3e7f5eb4..d624890761b2a 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-07-03 +date: 2023-07-05 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 7efbd34be21d7..82fee9fc02d9e 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-07-03 +date: 2023-07-05 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 74a5b9517cec4..a70dbf66e069d 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-07-03 +date: 2023-07-05 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 20d1d82c6f814..fcdf5f386295d 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-07-03 +date: 2023-07-05 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 3446f042583a1..bb7b848848e3f 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-07-03 +date: 2023-07-05 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 937a47e7be8c4..b8397b606fbad 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-07-03 +date: 2023-07-05 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 e4ab55019da23..e62c03def083c 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualization_ui_components.mdx b/api_docs/visualization_ui_components.mdx index 4df3e47fce1b6..e513affacd64f 100644 --- a/api_docs/visualization_ui_components.mdx +++ b/api_docs/visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizationUiComponents title: "visualizationUiComponents" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizationUiComponents plugin -date: 2023-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizationUiComponents'] --- import visualizationUiComponentsObj from './visualization_ui_components.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index b208bb40e55e7..6d60a643b0492 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-07-03 +date: 2023-07-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/config/serverless.yml b/config/serverless.yml index b7f2833810d0a..e3ecaaa44a8d3 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -53,8 +53,9 @@ server.versioned.versionResolution: newest # do not enforce client version check server.versioned.strictClientVersionCheck: false -# Enforce single "default" space +# Enforce single "default" space and disable feature visibility controls xpack.spaces.maxSpaces: 1 +xpack.spaces.allowFeatureVisibility: false # Temporarily allow unauthenticated access to task manager utilization & status/stats APIs for autoscaling status.allowAnonymous: true diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 4685d40e5ddbf..5d915c8feb6a6 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -54,7 +54,6 @@ Review the following information about the {kib} 8.8.2 release. === Bug Fixes APM:: -* Circuit breaker and performance improvements for service map {kibana-pull}159883[#159883] * Fixes the latency graph displaying all service transactions, rather than the selected one, on the transaction detail page {kibana-pull}159085[#159085] Dashboard:: diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 919703efff78f..8e001dcd6fdc6 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -628,6 +628,10 @@ the infrastructure monitoring use-case within Kibana. using the CURL scripts in the scripts folder. +|{kib-repo}blob/{branch}/x-pack/plugins/logs_shared/README.md[logsShared] +|Exposes the shared components and APIs to access and visualize logs. + + |{kib-repo}blob/{branch}/x-pack/plugins/logstash[logstash] |WARNING: Missing README. diff --git a/docs/setup/access.asciidoc b/docs/setup/access.asciidoc index 2b76843b5964d..631fa22d711dd 100644 --- a/docs/setup/access.asciidoc +++ b/docs/setup/access.asciidoc @@ -61,5 +61,5 @@ curl -XGET elasticsearch_ip_or_hostname:9200/_cat/indices/.kibana,.kibana_task_m + For example: -* When {kib} is unable to connect to a healthy {es} cluster, the `master_not_discovered_exception` or `Unable to revive connection` errors appear. +* When {kib} is unable to connect to a healthy {es} cluster, errors like `master_not_discovered_exception` or `unable to revive connection` or `license is not available` errors appear. * When one or more {kib}-backing indices are unhealthy, the `index_not_green_timeout` error appears. diff --git a/fleet_packages.json b/fleet_packages.json index 208d8f079abc8..c4fbbc9750147 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -24,7 +24,7 @@ [ { "name": "apm", - "version": "8.10.0-preview-1687509840", + "version": "8.10.0-preview-1688475406", "forceAlignStackVersion": true, "allowSyncToPrerelease": true }, @@ -58,6 +58,6 @@ }, { "name": "security_detection_engine", - "version": "8.8.6" + "version": "8.9.1" } ] \ No newline at end of file diff --git a/package.json b/package.json index 9a3366b65ee29..7658626741ea1 100644 --- a/package.json +++ b/package.json @@ -487,6 +487,7 @@ "@kbn/locator-explorer-plugin": "link:examples/locator_explorer", "@kbn/logging": "link:packages/kbn-logging", "@kbn/logging-mocks": "link:packages/kbn-logging-mocks", + "@kbn/logs-shared-plugin": "link:x-pack/plugins/logs_shared", "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", "@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation", "@kbn/management-plugin": "link:src/plugins/management", diff --git a/packages/core/http/core-http-router-server-internal/src/router.ts b/packages/core/http/core-http-router-server-internal/src/router.ts index 7aede8bfc01a7..509f407ff401a 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.ts @@ -7,6 +7,7 @@ */ import type { Request, ResponseToolkit } from '@hapi/hapi'; +import apm from 'elastic-apm-node'; import { isConfigSchema } from '@kbn/config-schema'; import type { Logger } from '@kbn/logging'; import { @@ -206,18 +207,24 @@ export class Router = undefined; + public get versioned(): VersionedRouter { if (this.versionedRouter === undefined) { this.versionedRouter = CoreVersionedRouter.from({ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts index 17c55102a3cc7..4e04cc2a70539 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts @@ -20,8 +20,6 @@ jest.mock('../core/build_active_mappings'); const getUpdatedHashesMock = getUpdatedHashes as jest.MockedFn; -const indexTypes = ['type1', 'type2']; - const properties: SavedObjectsMappingProperties = { type1: { type: 'long' }, type2: { type: 'long' }, @@ -48,7 +46,6 @@ describe('checkTargetMappings', () => { describe('when actual mappings are incomplete', () => { it("returns 'actual_mappings_incomplete' if actual mappings are not defined", async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, }); @@ -58,7 +55,6 @@ describe('checkTargetMappings', () => { it("returns 'actual_mappings_incomplete' if actual mappings do not define _meta", async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, actualMappings: { properties, @@ -72,7 +68,6 @@ describe('checkTargetMappings', () => { it("returns 'actual_mappings_incomplete' if actual mappings do not define migrationMappingPropertyHashes", async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, actualMappings: { properties, @@ -87,7 +82,6 @@ describe('checkTargetMappings', () => { it("returns 'actual_mappings_incomplete' if actual mappings define a different value for 'dynamic' property", async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, actualMappings: { properties, @@ -105,7 +99,6 @@ describe('checkTargetMappings', () => { describe('and mappings do not match', () => { it('returns the lists of changed root fields and types', async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, actualMappings: expectedMappings, }); @@ -115,8 +108,7 @@ describe('checkTargetMappings', () => { const result = await task(); const expected: ComparedMappingsChanged = { type: 'compared_mappings_changed' as const, - updatedRootFields: ['someRootField'], - updatedTypes: ['type1', 'type2'], + updatedHashes: ['type1', 'type2', 'someRootField'], }; expect(result).toEqual(Either.left(expected)); }); @@ -125,7 +117,6 @@ describe('checkTargetMappings', () => { describe('and mappings match', () => { it('returns a compared_mappings_match response', async () => { const task = checkTargetMappings({ - indexTypes, expectedMappings, actualMappings: expectedMappings, }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts index 26e0f074f43cb..576459beadd74 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts @@ -13,7 +13,6 @@ import { getUpdatedHashes } from '../core/build_active_mappings'; /** @internal */ export interface CheckTargetMappingsParams { - indexTypes: string[]; actualMappings?: IndexMapping; expectedMappings: IndexMapping; } @@ -29,13 +28,11 @@ export interface ActualMappingsIncomplete { export interface ComparedMappingsChanged { type: 'compared_mappings_changed'; - updatedRootFields: string[]; - updatedTypes: string[]; + updatedHashes: string[]; } export const checkTargetMappings = ({ - indexTypes, actualMappings, expectedMappings, }: CheckTargetMappingsParams): TaskEither.TaskEither< @@ -56,12 +53,9 @@ export const checkTargetMappings = }); if (updatedHashes.length) { - const updatedTypes = updatedHashes.filter((field) => indexTypes.includes(field)); - const updatedRootFields = updatedHashes.filter((field) => !indexTypes.includes(field)); return Either.left({ type: 'compared_mappings_changed' as const, - updatedRootFields, - updatedTypes, + updatedHashes, }); } else { return Either.right({ type: 'compared_mappings_match' as const }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.test.ts new file mode 100644 index 0000000000000..3895bd9314df3 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { buildPickupMappingsQuery } from './build_pickup_mappings_query'; + +describe('buildPickupMappingsQuery', () => { + describe('when no root fields have been updated', () => { + it('builds a boolean query to select the updated types', () => { + const query = buildPickupMappingsQuery(['type1', 'type2']); + + expect(query).toEqual({ + bool: { + should: [{ term: { type: 'type1' } }, { term: { type: 'type2' } }], + }, + }); + }); + }); + + describe('when some root fields have been updated', () => { + it('returns undefined', () => { + const query = buildPickupMappingsQuery(['type1', 'type2', 'namespaces']); + + expect(query).toBeUndefined(); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.ts new file mode 100644 index 0000000000000..ce110f72f66c1 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_pickup_mappings_query.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 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { getBaseMappings } from './build_active_mappings'; + +export const buildPickupMappingsQuery = ( + updatedFields: string[] +): QueryDslQueryContainer | undefined => { + const rootFields = Object.keys(getBaseMappings().properties); + + if (updatedFields.some((field) => rootFields.includes(field))) { + // we are updating some root fields, update ALL documents (no filter query) + return undefined; + } + + // at this point, all updated fields correspond to SO types + const updatedTypes = updatedFields; + + return { + bool: { + should: updatedTypes.map((type) => ({ term: { type } })), + }, + }; +}; 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 39a03e1e28d47..7039ce1cd1afa 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 @@ -2615,8 +2615,7 @@ describe('migrations v2 model', () => { it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES if core fields have been updated', () => { const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.left({ type: 'compared_mappings_changed' as const, - updatedRootFields: ['namespaces'], - updatedTypes: ['dashboard', 'lens'], + updatedHashes: ['dashboard', 'lens', 'namespaces'], }); const newState = model( checkTargetMappingsState, @@ -2631,8 +2630,7 @@ describe('migrations v2 model', () => { it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES if only SO types have changed', () => { const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.left({ type: 'compared_mappings_changed' as const, - updatedRootFields: [], - updatedTypes: ['dashboard', 'lens'], + updatedHashes: ['dashboard', 'lens'], }); const newState = model( checkTargetMappingsState, 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 2505581c6bc3d..2264ca388c975 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 @@ -54,6 +54,8 @@ import { CLUSTER_SHARD_LIMIT_EXCEEDED_REASON, FATAL_REASON_REQUEST_ENTITY_TOO_LARGE, } from '../common/constants'; +import { getBaseMappings } from '../core'; +import { buildPickupMappingsQuery } from '../core/build_pickup_mappings_query'; export const model = (currentState: State, resW: ResponseType): State => { // The action response `resW` is weakly typed, the type includes all action @@ -1439,18 +1441,22 @@ export const model = (currentState: State, resW: ResponseType): updatedTypesQuery: Option.none, }; } else if (isTypeof(left, 'compared_mappings_changed')) { - if (left.updatedRootFields.length) { + const rootFields = Object.keys(getBaseMappings().properties); + const updatedRootFields = left.updatedHashes.filter((field) => rootFields.includes(field)); + const updatedTypesQuery = Option.fromNullable(buildPickupMappingsQuery(left.updatedHashes)); + + if (updatedRootFields.length) { // compatible migration: some core fields have been updated return { ...stateP, controlState: 'UPDATE_TARGET_MAPPINGS_PROPERTIES', // we must "pick-up" all documents on the index (by not providing a query) - updatedTypesQuery: Option.none, + updatedTypesQuery, logs: [ ...stateP.logs, { level: 'info', - message: `Kibana is performing a compatible upgrade and the mappings of some root fields have been changed. For Elasticsearch to pickup these mappings, all saved objects need to be updated. Updated root fields: ${left.updatedRootFields}.`, + message: `Kibana is performing a compatible upgrade and the mappings of some root fields have been changed. For Elasticsearch to pickup these mappings, all saved objects need to be updated. Updated root fields: ${updatedRootFields}.`, }, ], }; @@ -1460,16 +1466,12 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, controlState: 'UPDATE_TARGET_MAPPINGS_PROPERTIES', // we can "pick-up" only the SO types that have changed - updatedTypesQuery: Option.some({ - bool: { - should: left.updatedTypes.map((type) => ({ term: { type } })), - }, - }), + updatedTypesQuery, logs: [ ...stateP.logs, { level: 'info', - message: `Kibana is performing a compatible upgrade and NO root fields have been udpated. Kibana will update the following SO types so that ES can pickup the updated mappings: ${left.updatedTypes}.`, + message: `Kibana is performing a compatible upgrade and NO root fields have been udpated. Kibana will update the following SO types so that ES can pickup the updated mappings: ${left.updatedHashes}.`, }, ], }; 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 df7e0c23fbc20..b8d4afc55631d 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 @@ -58,7 +58,6 @@ import { createDelayFn } from './common/utils'; import type { TransformRawDocs } from './types'; import * as Actions from './actions'; import { REMOVED_TYPES } from './core'; -import { getIndexTypes } from './model/helpers'; type ActionMap = ReturnType; @@ -202,7 +201,6 @@ export const nextActionMap = ( Actions.checkTargetMappings({ actualMappings: Option.toUndefined(state.sourceIndexMappings), expectedMappings: state.targetIndexMappings, - indexTypes: getIndexTypes(state), }), UPDATE_TARGET_MAPPINGS_PROPERTIES: (state: UpdateTargetMappingsPropertiesState) => Actions.updateAndPickupMappings({ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts index 00684ec46f85c..6c2f90155ac76 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts @@ -22,6 +22,7 @@ import type { SetDocMigrationStartedState, UpdateMappingModelVersionState, UpdateDocumentModelVersionsState, + UpdateIndexMappingsState, } from './state'; describe('actions', () => { @@ -147,4 +148,67 @@ describe('actions', () => { }); }); }); + + describe('UPDATE_INDEX_MAPPINGS', () => { + describe('when only SO types have been updated', () => { + it('calls updateAndPickupMappings with the correct parameters', () => { + const state: UpdateIndexMappingsState = { + ...createPostDocInitState(), + controlState: 'UPDATE_INDEX_MAPPINGS', + additiveMappingChanges: { + someToken: {}, + }, + }; + const action = actionMap.UPDATE_INDEX_MAPPINGS; + + action(state); + + expect(ActionMocks.updateAndPickupMappings).toHaveBeenCalledTimes(1); + expect(ActionMocks.updateAndPickupMappings).toHaveBeenCalledWith({ + client: context.elasticsearchClient, + index: state.currentIndex, + mappings: { + properties: { + someToken: {}, + }, + }, + batchSize: context.batchSize, + query: { + bool: { + should: [{ term: { type: 'someToken' } }], + }, + }, + }); + }); + }); + + describe('when core properties have been updated', () => { + it('calls updateAndPickupMappings with the correct parameters', () => { + const state: UpdateIndexMappingsState = { + ...createPostDocInitState(), + controlState: 'UPDATE_INDEX_MAPPINGS', + additiveMappingChanges: { + managed: {}, // this is a root field + someToken: {}, + }, + }; + const action = actionMap.UPDATE_INDEX_MAPPINGS; + + action(state); + + expect(ActionMocks.updateAndPickupMappings).toHaveBeenCalledTimes(1); + expect(ActionMocks.updateAndPickupMappings).toHaveBeenCalledWith({ + client: context.elasticsearchClient, + index: state.currentIndex, + mappings: { + properties: { + managed: {}, + someToken: {}, + }, + }, + batchSize: context.batchSize, + }); + }); + }); + }); }); 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 index 17749104d18fe..fe1093b0f4978 100644 --- 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 @@ -39,6 +39,7 @@ import { setMetaDocMigrationComplete, setMetaDocMigrationStarted, } from './utils'; +import { buildPickupMappingsQuery } from '../core/build_pickup_mappings_query'; export type ActionMap = ReturnType; @@ -72,6 +73,7 @@ export const nextActionMap = (context: MigratorContext) => { index: state.currentIndex, mappings: { properties: state.additiveMappingChanges }, batchSize: context.batchSize, + query: buildPickupMappingsQuery(Object.keys(state.additiveMappingChanges)), }), UPDATE_INDEX_MAPPINGS_WAIT_FOR_TASK: (state: UpdateIndexMappingsWaitForTaskState) => Actions.waitForPickupUpdatedMappingsTask({ diff --git a/packages/deeplinks/management/deep_links.ts b/packages/deeplinks/management/deep_links.ts index 1fa3d0f7d30bd..0e9b16dd7dc8f 100644 --- a/packages/deeplinks/management/deep_links.ts +++ b/packages/deeplinks/management/deep_links.ts @@ -54,4 +54,8 @@ export type ManagementDeepLinkId = MonitoringAppId | `${ManagementAppId}:${Manag // Combined export type AppId = MonitoringAppId | IntegrationsAppId | ManagementAppId; export type LinkId = ManagementId; -export type DeepLinkId = MonitoringDeepLinkId | IntegrationsDeepLinkId | ManagementDeepLinkId; +export type DeepLinkId = + | AppId + | MonitoringDeepLinkId + | IntegrationsDeepLinkId + | ManagementDeepLinkId; diff --git a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx index 5877added643f..5820e7a971e90 100644 --- a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx +++ b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx @@ -192,7 +192,7 @@ const getParentCellElement = (element?: HTMLElement | null): HTMLElement | null if (element == null) { return null; } - if (element.nodeName === 'div' && element.getAttribute('role') === 'gridcell') { + if (element.nodeName === 'DIV' && element.getAttribute('role') === 'gridcell') { return element; } return getParentCellElement(element.parentElement); diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index b47b0ab86ac3b..304c249c7e2d4 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -521,6 +521,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { syntheticsCommandReference: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-configuration.html#synthetics-configuration-playwright-options`, syntheticsProjectMonitors: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetic-run-tests.html#synthetic-monitor-choose-project`, syntheticsMigrateFromIntegration: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-migrate-from-integration.html`, + sloBurnRateRule: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/slo-burn-rate-alert.html`, }, alerting: { guide: `${KIBANA_DOCS}create-and-manage-rules.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 5f6b92ebd2a44..0fe2e7b30047f 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -401,6 +401,7 @@ export interface DocLinks { syntheticsCommandReference: string; syntheticsProjectMonitors: string; syntheticsMigrateFromIntegration: string; + sloBurnRateRule: string; }>; readonly alerting: Readonly<{ guide: string; diff --git a/packages/kbn-es/src/utils/extract_config_files.test.js b/packages/kbn-es/src/utils/extract_config_files.test.js index a78dc8111707f..313a8c68b0194 100644 --- a/packages/kbn-es/src/utils/extract_config_files.test.js +++ b/packages/kbn-es/src/utils/extract_config_files.test.js @@ -10,6 +10,11 @@ jest.mock('fs', () => ({ readFileSync: jest.fn(), existsSync: jest.fn().mockImplementation(() => true), writeFileSync: jest.fn(), + statSync: jest.fn((fileName) => { + return { + isFile: () => fileName.endsWith('.yml'), + }; + }), })); const { extractConfigFiles } = require('./extract_config_files'); @@ -63,3 +68,10 @@ test('ignores directories', () => { expect(config).toEqual(['path=foo.yml', 'foo.bar=/data/bar']); }); + +test('ignores directories with dots in their names', () => { + fs.existsSync = () => true; + const config = extractConfigFiles(['path=/data/foo.yml', 'foo.bar=/data/ba/r.baz'], '/es'); + + expect(config).toEqual(['path=foo.yml', 'foo.bar=/data/ba/r.baz']); +}); diff --git a/packages/kbn-es/src/utils/extract_config_files.ts b/packages/kbn-es/src/utils/extract_config_files.ts index ff07c77258d05..908005887dbc0 100644 --- a/packages/kbn-es/src/utils/extract_config_files.ts +++ b/packages/kbn-es/src/utils/extract_config_files.ts @@ -29,6 +29,7 @@ export function extractConfigFiles( if (isFile(value)) { const filename = path.basename(value); const destPath = path.resolve(dest, 'config', filename); + copyFileSync(value, destPath); options?.log.info('moved %s in config to %s', value, destPath); @@ -43,7 +44,7 @@ export function extractConfigFiles( } function isFile(dest = '') { - return path.isAbsolute(dest) && path.extname(dest).length > 0 && fs.existsSync(dest); + return fs.existsSync(dest) && fs.statSync(dest).isFile(); } function copyFileSync(src: string, dest: string) { diff --git a/packages/kbn-io-ts-utils/src/in_range_rt/index.ts b/packages/kbn-io-ts-utils/src/in_range_rt/index.ts index b632173cb69d9..99d04b3f6abc6 100644 --- a/packages/kbn-io-ts-utils/src/in_range_rt/index.ts +++ b/packages/kbn-io-ts-utils/src/in_range_rt/index.ts @@ -16,11 +16,15 @@ export interface InRangeBrand { export type InRange = rt.Branded; export const inRangeRt = (start: number, end: number) => - rt.brand( - rt.number, // codec - (n): n is InRange => n >= start && n <= end, - // refinement of the number type - 'InRange' // name of this codec + new rt.Type( + 'InRange', + (input: unknown): input is number => + typeof input === 'number' && input >= start && input <= end, + (input: unknown, context: rt.Context) => + typeof input === 'number' && input >= start && input <= end + ? rt.success(input) + : rt.failure(input, context), + rt.identity ); export const inRangeFromStringRt = (start: number, end: number) => { diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index eb7ca100c56f8..c0a9f99b09d4c 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -88,6 +88,7 @@ pageLoadAssetSize: licenseManagement: 41817 licensing: 29004 lists: 22900 + logsShared: 281060 logstash: 53548 management: 46112 maps: 90000 diff --git a/src/core/server/integration_tests/http/router.test.mocks.ts b/src/core/server/integration_tests/http/router.test.mocks.ts new file mode 100644 index 0000000000000..fd2862acd437c --- /dev/null +++ b/src/core/server/integration_tests/http/router.test.mocks.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 captureErrorMock = jest.fn(); + +jest.doMock('elastic-apm-node', () => { + const real = jest.requireActual('elastic-apm-node'); + return { + ...real, + captureError: captureErrorMock, + }; +}); diff --git a/src/core/server/integration_tests/http/router.test.ts b/src/core/server/integration_tests/http/router.test.ts index c9dca25eea044..743624f2f46b3 100644 --- a/src/core/server/integration_tests/http/router.test.ts +++ b/src/core/server/integration_tests/http/router.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { captureErrorMock } from './router.test.mocks'; + import { Stream } from 'stream'; import Boom from '@hapi/boom'; import supertest from 'supertest'; @@ -35,6 +37,7 @@ beforeEach(async () => { }); afterEach(async () => { + captureErrorMock.mockReset(); await server.stop(); }); @@ -581,6 +584,22 @@ describe('Handler', () => { `); }); + it('captures the error if handler throws', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const error = new Error(`some error`); + router.get({ path: '/', validate: false }, (context, req, res) => { + throw error; + }); + await server.start(); + + await supertest(innerServer.listener).get('/').expect(500); + + expect(captureErrorMock).toHaveBeenCalledTimes(1); + expect(captureErrorMock).toHaveBeenCalledWith(error); + }); + it('returns 500 Server error if handler throws Boom error', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); @@ -602,6 +621,7 @@ describe('Handler', () => { ], ] `); + expect(captureErrorMock).toHaveBeenCalledTimes(1); }); it('returns 500 Server error if handler returns unexpected result', async () => { diff --git a/src/core/server/integration_tests/http/versioned_router.test.mocks.ts b/src/core/server/integration_tests/http/versioned_router.test.mocks.ts new file mode 100644 index 0000000000000..fd2862acd437c --- /dev/null +++ b/src/core/server/integration_tests/http/versioned_router.test.mocks.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 captureErrorMock = jest.fn(); + +jest.doMock('elastic-apm-node', () => { + const real = jest.requireActual('elastic-apm-node'); + return { + ...real, + captureError: captureErrorMock, + }; +}); diff --git a/src/core/server/integration_tests/http/versioned_router.test.ts b/src/core/server/integration_tests/http/versioned_router.test.ts index 0fbd64acad9ce..adde119815d78 100644 --- a/src/core/server/integration_tests/http/versioned_router.test.ts +++ b/src/core/server/integration_tests/http/versioned_router.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { captureErrorMock } from './versioned_router.test.mocks'; + import Supertest from 'supertest'; import { createTestEnv, getEnvOptions } from '@kbn/config-mocks'; import { schema } from '@kbn/config-schema'; @@ -59,6 +61,7 @@ describe('Routing versioned requests', () => { }); afterEach(async () => { + captureErrorMock.mockReset(); await server.stop(); }); @@ -167,6 +170,7 @@ describe('Routing versioned requests', () => { message: expect.stringMatching(/expected value of type/), }) ); + expect(captureErrorMock).not.toHaveBeenCalled(); }); it('returns the version in response headers', async () => { @@ -210,6 +214,7 @@ describe('Routing versioned requests', () => { message: expect.stringMatching(/Failed output validation/), }) ); + expect(captureErrorMock).not.toHaveBeenCalled(); }); it('does not run response validation in prod', async () => { @@ -295,6 +300,7 @@ describe('Routing versioned requests', () => { ).resolves.toEqual( expect.objectContaining({ message: expect.stringMatching(/No handlers registered/) }) ); + expect(captureErrorMock).not.toHaveBeenCalled(); }); it('resolves the newest handler on serverless', async () => { @@ -340,4 +346,21 @@ describe('Routing versioned requests', () => { .then(({ body }) => body.v) ).resolves.toEqual('oldest'); }); + + it('captures the error if handler throws', async () => { + const error = new Error(`some error`); + + router.versioned + .get({ path: '/my-path', access: 'internal' }) + .addVersion({ validate: false, version: '1' }, async (ctx, req, res) => { + throw error; + }); + + await server.start(); + + await supertest.get('/my-path').set('Elastic-Api-Version', '1').expect(500); + + expect(captureErrorMock).toHaveBeenCalledTimes(1); + expect(captureErrorMock).toHaveBeenCalledWith(error); + }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts index 258a33fe591d6..30888521d651e 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts @@ -412,9 +412,8 @@ describe('split .kibana index into multiple system indices', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/157510 - // This test takes too long. Can be manually executed to verify the correct behavior. - describe.skip('when multiple Kibana migrators run in parallel', () => { + describe('when multiple Kibana migrators run in parallel', () => { + jest.setTimeout(1200000); it('correctly migrates 7.7.2_xpack_100k_obj.zip archive', async () => { esServer = await startElasticsearch({ dataArchive: Path.join(__dirname, '..', 'archives', '7.7.2_xpack_100k_obj.zip'), @@ -491,7 +490,7 @@ describe('split .kibana index into multiple system indices', () => { task: 5, }, }); - }, 1200000); + }); afterEach(async () => { await esServer?.stop(); diff --git a/src/plugins/data_views/common/content_management/v1/cm_services.ts b/src/plugins/data_views/common/content_management/v1/cm_services.ts index 7ff2d135a9d90..e425a40cce433 100644 --- a/src/plugins/data_views/common/content_management/v1/cm_services.ts +++ b/src/plugins/data_views/common/content_management/v1/cm_services.ts @@ -54,6 +54,7 @@ const dataViewSavedObjectSchema = savedObjectSchema(dataViewAttributesSchema); const dataViewCreateOptionsSchema = schema.object({ id: createOptionsSchemas.id, initialNamespaces: createOptionsSchemas.initialNamespaces, + overwrite: schema.maybe(createOptionsSchemas.overwrite), }); const dataViewSearchOptionsSchema = schema.object({ diff --git a/src/plugins/data_views/common/content_management/v1/types.ts b/src/plugins/data_views/common/content_management/v1/types.ts index 1bada30b8dd94..19637bb17e8c5 100644 --- a/src/plugins/data_views/common/content_management/v1/types.ts +++ b/src/plugins/data_views/common/content_management/v1/types.ts @@ -18,6 +18,7 @@ import { DataViewContentType } from './constants'; interface DataViewCreateOptions { id?: SavedObjectCreateOptions['id']; initialNamespaces?: SavedObjectCreateOptions['initialNamespaces']; + overwrite?: SavedObjectCreateOptions['overwrite']; } interface DataViewUpdateOptions { 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 947e8884bc7cc..f51189f2409ea 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -167,7 +167,7 @@ export interface DataViewsServicePublicMethods { */ createSavedObject: ( indexPattern: DataView, - override?: boolean, + overwrite?: boolean, displayErrors?: boolean ) => Promise; /** @@ -964,12 +964,16 @@ export class DataViewsService { async createAndSave( spec: DataViewSpec, - override = false, + overwrite = false, skipFetchFields = false, displayErrors = true ) { const indexPattern = await this.createFromSpec(spec, skipFetchFields, displayErrors); - const createdIndexPattern = await this.createSavedObject(indexPattern, override, displayErrors); + const createdIndexPattern = await this.createSavedObject( + indexPattern, + overwrite, + displayErrors + ); await this.setDefault(createdIndexPattern.id!); return createdIndexPattern!; } @@ -981,14 +985,14 @@ export class DataViewsService { * @param displayErrors - If set false, API consumer is responsible for displaying and handling errors. */ - async createSavedObject(dataView: DataView, override = false, displayErrors = true) { + async createSavedObject(dataView: DataView, overwrite = false, displayErrors = true) { if (!(await this.getCanSave())) { throw new DataViewInsufficientAccessError(); } const dupe = await findByName(this.savedObjectsClient, dataView.getName()); if (dupe) { - if (override) { + if (overwrite) { await this.delete(dupe.id); } else { throw new DuplicateDataViewError(`Duplicate data view: ${dataView.getName()}`); @@ -1000,6 +1004,7 @@ export class DataViewsService { const response: SavedObject = (await this.savedObjectsClient.create(body, { id: dataView.id, initialNamespaces: dataView.namespaces.length > 0 ? dataView.namespaces : undefined, + overwrite, })) as SavedObject; const createdIndexPattern = await this.initFromSavedObject(response, displayErrors); diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index 6c42664c90ffb..aa157d5336685 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -297,7 +297,7 @@ export interface SavedObjectsClientCommon { create: ( attributes: DataViewAttributes, // SavedObjectsCreateOptions - options: { id?: string; initialNamespaces?: string[] } + options: { id?: string; initialNamespaces?: string[]; overwrite?: boolean } ) => Promise; /** * Delete a saved object by id diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index bcf9aef7d80f1..0316ddd1b383d 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -183,18 +183,21 @@ describe('Test createSearchSessionRestorationDataProvider', () => { let mockSavedSearch: SavedSearch = {} as unknown as SavedSearch; const history = createBrowserHistory(); const mockDataPlugin = dataPluginMock.createStartContract(); + const discoverStateContainer = getDiscoverStateContainer({ + services: discoverServiceMock, + history, + }); + discoverStateContainer.appState.update({ + index: savedSearchMock.searchSource.getField('index')!.id, + }); const searchSessionInfoProvider = createSearchSessionRestorationDataProvider({ data: mockDataPlugin, - appStateContainer: getDiscoverStateContainer({ - savedSearch: savedSearchMock, - services: discoverServiceMock, - history, - }).appState, + appStateContainer: discoverStateContainer.appState, getSavedSearch: () => mockSavedSearch, }); describe('session name', () => { - test('No saved search returns default name', async () => { + test('No persisted saved search returns default name', async () => { expect(await searchSessionInfoProvider.getName()).toBe('Discover'); }); @@ -211,6 +214,7 @@ describe('Test createSearchSessionRestorationDataProvider', () => { describe('session state', () => { test('restoreState has sessionId and initialState has not', async () => { + mockSavedSearch = savedSearchMock; const searchSessionId = 'id'; (mockDataPlugin.search.session.getSessionId as jest.Mock).mockImplementation( () => searchSessionId @@ -221,6 +225,7 @@ describe('Test createSearchSessionRestorationDataProvider', () => { }); test('restoreState has absoluteTimeRange', async () => { + mockSavedSearch = savedSearchMock; const relativeTime = 'relativeTime'; const absoluteTime = 'absoluteTime'; (mockDataPlugin.query.timefilter.timefilter.getTime as jest.Mock).mockImplementation( @@ -235,6 +240,7 @@ describe('Test createSearchSessionRestorationDataProvider', () => { }); test('restoreState has paused autoRefresh', async () => { + mockSavedSearch = savedSearchMock; const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); expect(initialState.refreshInterval).toBe(undefined); expect(restoreState.refreshInterval).toEqual({ @@ -242,6 +248,21 @@ describe('Test createSearchSessionRestorationDataProvider', () => { value: 0, }); }); + + test('restoreState has persisted data view', async () => { + mockSavedSearch = savedSearchMock; + const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); + expect(initialState.dataViewSpec).toEqual(undefined); + expect(restoreState.dataViewSpec).toEqual(undefined); + expect(initialState.dataViewId).toEqual(savedSearchMock.searchSource.getField('index')?.id); + }); + + test('restoreState has temporary data view', async () => { + mockSavedSearch = savedSearchAdHoc; + const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); + expect(initialState.dataViewSpec).toEqual({}); + expect(restoreState.dataViewSpec).toEqual({}); + }); }); }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 79eea9eed9964..e80f6e5c338b7 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -476,7 +476,7 @@ export function createSearchSessionRestorationDataProvider(deps: { data: DataPublicPluginStart; getSavedSearch: () => SavedSearch; }): SearchSessionInfoProvider { - const getSavedSearchId = () => deps.getSavedSearch().id; + const getSavedSearch = () => deps.getSavedSearch(); return { getName: async () => { const savedSearch = deps.getSavedSearch(); @@ -492,12 +492,12 @@ export function createSearchSessionRestorationDataProvider(deps: { id: DISCOVER_APP_LOCATOR, initialState: createUrlGeneratorState({ ...deps, - getSavedSearchId, + getSavedSearch, shouldRestoreSearchSession: false, }), restoreState: createUrlGeneratorState({ ...deps, - getSavedSearchId, + getSavedSearch, shouldRestoreSearchSession: true, }), }; @@ -508,20 +508,21 @@ export function createSearchSessionRestorationDataProvider(deps: { function createUrlGeneratorState({ appStateContainer, data, - getSavedSearchId, + getSavedSearch, shouldRestoreSearchSession, }: { appStateContainer: StateContainer; data: DataPublicPluginStart; - getSavedSearchId: () => string | undefined; + getSavedSearch: () => SavedSearch; shouldRestoreSearchSession: boolean; }): DiscoverAppLocatorParams { const appState = appStateContainer.get(); + const dataView = getSavedSearch().searchSource.getField('index'); return { filters: data.query.filterManager.getFilters(), dataViewId: appState.index, query: appState.query, - savedSearchId: getSavedSearchId(), + savedSearchId: getSavedSearch().id, timeRange: shouldRestoreSearchSession ? data.query.timefilter.timefilter.getAbsoluteTime() : data.query.timefilter.timefilter.getTime(), @@ -540,5 +541,6 @@ function createUrlGeneratorState({ viewMode: appState.viewMode, hideAggregatedPreview: appState.hideAggregatedPreview, breakdownField: appState.breakdownField, + dataViewSpec: !dataView?.isPersisted() ? dataView?.toSpec(false) : undefined, }; } diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts index d846f7b949513..fc6a1ae9d166e 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts @@ -70,7 +70,7 @@ export async function loadDataView({ let fetchedDataView: DataView | null = null; // try to fetch adhoc data view first try { - fetchedDataView = fetchId ? await dataViews.get(fetchId, false) : null; + fetchedDataView = fetchId ? await dataViews.get(fetchId, false, true) : null; if (fetchedDataView && !fetchedDataView.isPersisted()) { return { list: dataViewList || [], @@ -88,7 +88,10 @@ export async function loadDataView({ let defaultDataView: DataView | null = null; if (!fetchedDataView) { try { - defaultDataView = await dataViews.getDefaultDataView({ displayErrors: false }); + defaultDataView = await dataViews.getDefaultDataView({ + displayErrors: false, + refreshFields: true, + }); } catch (e) { // } diff --git a/src/plugins/event_annotation/common/content_management/v1/types.ts b/src/plugins/event_annotation/common/content_management/v1/types.ts index a5c2306100821..d3370b9ff28bf 100644 --- a/src/plugins/event_annotation/common/content_management/v1/types.ts +++ b/src/plugins/event_annotation/common/content_management/v1/types.ts @@ -120,6 +120,9 @@ export interface EventAnnotationGroupSearchQuery { searchFields?: string[]; } -export type EventAnnotationGroupSearchIn = SearchIn; +export type EventAnnotationGroupSearchIn = SearchIn< + EventAnnotationGroupContentType, + EventAnnotationGroupSearchQuery +>; export type EventAnnotationGroupSearchOut = SearchResult; diff --git a/src/plugins/event_annotation/public/components/event_annotation_group_saved_object_finder.tsx b/src/plugins/event_annotation/public/components/event_annotation_group_saved_object_finder.tsx index 34b1ce7eb0694..72f931797d3c4 100644 --- a/src/plugins/event_annotation/public/components/event_annotation_group_saved_object_finder.tsx +++ b/src/plugins/event_annotation/public/components/event_annotation_group_saved_object_finder.tsx @@ -67,35 +67,37 @@ export const EventAnnotationGroupSavedObjectFinder = ({ direction="column" justifyContent="center" > - - - - } - body={ - -

+ + -

-
- } - actions={ - onCreateNew()} size="s"> - - - } - /> + + } + body={ + +

+ +

+
+ } + actions={ + onCreateNew()} size="s"> + + + } + /> + ) : ( => { const savedObject = await client.get({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, id: savedObjectId, }); @@ -117,6 +118,29 @@ export function getEventAnnotationService( return mapSavedObjectToGroupConfig(savedObject.item); }; + const groupExistsWithTitle = async (title: string): Promise => { + const { hits } = await client.search< + EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut + >({ + contentTypeId: CONTENT_ID, + query: { + text: title, + }, + options: { + searchFields: ['title'], + }, + }); + + for (const hit of hits) { + if (hit.attributes.title.toLowerCase() === title.toLowerCase()) { + return true; + } + } + + return false; + }; + const findAnnotationGroupContent = async ( searchTerm: string, pageSize: number, @@ -138,7 +162,7 @@ export function getEventAnnotationService( EventAnnotationGroupSearchIn, EventAnnotationGroupSearchOut >({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, query: { text: searchOptions.search, }, @@ -153,7 +177,7 @@ export function getEventAnnotationService( const deleteAnnotationGroups = async (ids: string[]): Promise => { for (const id of ids) { await client.delete({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, id, }); } @@ -223,7 +247,7 @@ export function getEventAnnotationService( const groupSavedObjectId = ( await client.create({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, data: { ...attributes, }, @@ -243,7 +267,7 @@ export function getEventAnnotationService( const { attributes, references } = getAnnotationGroupAttributesAndReferences(group); await client.update({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, id: annotationGroupId, data: { ...attributes, @@ -259,7 +283,7 @@ export function getEventAnnotationService( EventAnnotationGroupSearchIn, EventAnnotationGroupSearchOut >({ - contentTypeId: EVENT_ANNOTATION_GROUP_TYPE, + contentTypeId: CONTENT_ID, query: { text: '*', }, @@ -270,6 +294,7 @@ export function getEventAnnotationService( return { loadAnnotationGroup, + groupExistsWithTitle, updateAnnotationGroup, createAnnotationGroup, deleteAnnotationGroups, diff --git a/src/plugins/event_annotation/public/event_annotation_service/types.ts b/src/plugins/event_annotation/public/event_annotation_service/types.ts index 8742ea86afff8..8837792954b51 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/types.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/types.ts @@ -14,6 +14,7 @@ import { EventAnnotationConfig, EventAnnotationGroupConfig } from '../../common' export interface EventAnnotationServiceType { loadAnnotationGroup: (savedObjectId: string) => Promise; + groupExistsWithTitle: (title: string) => Promise; findAnnotationGroupContent: ( searchTerm: string, pageSize: number, diff --git a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts index ce032374e7b53..5755a7efe998f 100644 --- a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts +++ b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts @@ -268,9 +268,11 @@ export class EventAnnotationGroupStorage EventAnnotationGroupSearchQuery, EventAnnotationGroupSearchQuery >(options); + if (optionsError) { throw Boom.badRequest(`Invalid payload. ${optionsError.message}`); } + const { searchFields = ['title^3', 'description'], types = [SO_TYPE] } = optionsToLatest; const { included, excluded } = query.tags ?? {}; diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md index 354fe766d473a..828715235b1b9 100644 --- a/src/plugins/management/README.md +++ b/src/plugins/management/README.md @@ -38,4 +38,14 @@ If card needs to be hidden from the navigation you can specify that by using the }); ``` -More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. \ No newline at end of file +More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. + +## Landing page redirect + +If the consumer wants to have a separate landing page for the management section, they can use the `setLandingPageRedirect` +method to specify the path to the landing page: + + +``` + management.setLandingPageRedirect('/app/security/management'); +``` \ No newline at end of file diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index 133d19250085a..538da6045255a 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -43,6 +43,7 @@ export interface ManagementAppDependencies { setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; isSidebarEnabled$: BehaviorSubject; cardsNavigationConfig$: BehaviorSubject; + landingPageRedirect$: BehaviorSubject; } export const ManagementApp = ({ @@ -51,11 +52,13 @@ export const ManagementApp = ({ theme$, appBasePath, }: ManagementAppProps) => { - const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; + const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$, landingPageRedirect$ } = + dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); const isSidebarEnabled = useObservable(isSidebarEnabled$); const cardsNavigationConfig = useObservable(cardsNavigationConfig$); + const landingPageRedirect = useObservable(landingPageRedirect$); const onAppMounted = useCallback((id: string) => { setSelectedId(id); @@ -131,6 +134,9 @@ export const ManagementApp = ({ setBreadcrumbs={setBreadcrumbsScoped} onAppMounted={onAppMounted} sections={sections} + landingPageRedirect={landingPageRedirect} + navigateToUrl={dependencies.coreStart.application.navigateToUrl} + basePath={dependencies.coreStart.http.basePath} /> diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx index ad9a6e41989ce..9335af3bf5b78 100644 --- a/src/plugins/management/public/components/management_app/management_router.tsx +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -6,10 +6,12 @@ * Side Public License, v 1. */ -import React, { memo } from 'react'; +import React, { memo, useEffect } from 'react'; import { Redirect } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import type { HttpStart } from '@kbn/core-http-browser'; import { ManagementAppWrapper } from '../management_app_wrapper'; import { ManagementLandingPage } from '../landing'; import { ManagementSection } from '../../utils'; @@ -20,43 +22,65 @@ interface ManagementRouterProps { setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void; onAppMounted: (id: string) => void; sections: ManagementSection[]; + landingPageRedirect: string | undefined; + navigateToUrl: ApplicationStart['navigateToUrl']; + basePath: HttpStart['basePath']; } export const ManagementRouter = memo( - ({ history, setBreadcrumbs, onAppMounted, sections, theme$ }: ManagementRouterProps) => ( - - - {sections.map((section) => - section - .getAppsEnabled() - .map((app) => ( - ( - - )} - /> - )) - )} - {sections.map((section) => - section - .getAppsEnabled() - .filter((app) => app.redirectFrom) - .map((app) => ) - )} - ( - + ({ + history, + setBreadcrumbs, + onAppMounted, + sections, + theme$, + landingPageRedirect, + navigateToUrl, + basePath, + }: ManagementRouterProps) => { + // Redirect the user to the configured landing page if there is one + useEffect(() => { + if (landingPageRedirect) { + navigateToUrl(basePath.prepend(landingPageRedirect)); + } + }, [landingPageRedirect, navigateToUrl, basePath]); + + return ( + + + {sections.map((section) => + section + .getAppsEnabled() + .map((app) => ( + ( + + )} + /> + )) + )} + {sections.map((section) => + section + .getAppsEnabled() + .filter((app) => app.redirectFrom) + .map((app) => ) )} - /> - - - ) + + ( + + )} + /> + + + ); + } ); diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 93ccefbe5130f..bc6bdcdc46814 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -44,6 +44,7 @@ const createSetupContract = (): ManagementSetup => ({ const createStartContract = (): ManagementStart => ({ setIsSidebarEnabled: jest.fn(), setupCardsNavigation: jest.fn(), + setLandingPageRedirect: jest.fn(), }); export const managementPluginMock = { diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 712799de2f65c..445a9b3a3db0a 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -72,6 +72,7 @@ export class ManagementPlugin private hasAnyEnabledApps = true; private isSidebarEnabled$ = new BehaviorSubject(true); + private landingPageRedirect$ = new BehaviorSubject(undefined); private cardsNavigationConfig$ = new BehaviorSubject({ enabled: false, hideLinksTo: [], @@ -124,6 +125,7 @@ export class ManagementPlugin setBreadcrumbs: coreStart.chrome.setBreadcrumbs, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$, + landingPageRedirect$: managementPlugin.landingPageRedirect$, }); }, }); @@ -154,6 +156,8 @@ export class ManagementPlugin this.isSidebarEnabled$.next(isSidebarEnabled), setupCardsNavigation: ({ enabled, hideLinksTo }) => this.cardsNavigationConfig$.next({ enabled, hideLinksTo }), + setLandingPageRedirect: (landingPageRedirect: string) => + this.landingPageRedirect$.next(landingPageRedirect), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index e2b5353d8995c..52e66698629fd 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -30,6 +30,7 @@ export interface DefinedSections { export interface ManagementStart { setIsSidebarEnabled: (enabled: boolean) => void; + setLandingPageRedirect: (landingPageRedirect: string) => void; setupCardsNavigation: ({ enabled, hideLinksTo }: NavigationCardsSubject) => void; } diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 019867f387387..aeb231f2ea9ed 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -22,7 +22,9 @@ "@kbn/shared-ux-router", "@kbn/management-cards-navigation", "@kbn/shared-ux-link-redirect-app", - "@kbn/test-jest-helpers" + "@kbn/test-jest-helpers", + "@kbn/core-application-browser", + "@kbn/core-http-browser" ], "exclude": [ "target/**/*", diff --git a/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap index 70ba2d5619d03..e42ac60f9ba89 100644 --- a/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -28,9 +28,11 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` - - - + - - - - Save - + + + + + + + + Save + + + `; @@ -154,9 +154,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali - - - + - - - - Save - + + + + + + + + Save + + + `; @@ -280,9 +280,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali - - - + - - - - Save - + + + + + + + + Save + + + `; @@ -410,9 +410,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when given options - - - + - - - - Save - + + + + + + + + Save + + + `; diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index c471271ead7ed..b9bd5e5d7fe36 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -30,7 +30,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; export interface OnSaveProps { newTitle: string; @@ -93,11 +92,19 @@ export class SavedObjectSaveModal extends React.Component const hasColumns = !!this.props.rightOptions; + const titleInputValid = + hasAttemptedSubmit && + ((!isTitleDuplicateConfirmed && hasTitleDuplicate) || title.length === 0); + const formBodyContent = ( <> } + isInvalid={titleInputValid} + error={i18n.translate('savedObjects.saveModal.titleRequired', { + defaultMessage: 'A title is required', + })} > data-test-subj="savedObjectTitle" value={title} onChange={this.onTitleChange} - isInvalid={ - hasAttemptedSubmit && - ((!isTitleDuplicateConfirmed && hasTitleDuplicate) || title.length === 0) - } + isInvalid={titleInputValid} aria-describedby={this.state.hasTitleDuplicate ? duplicateWarningId : undefined} /> @@ -167,20 +171,19 @@ export class SavedObjectSaveModal extends React.Component - - {this.renderCopyOnSave()} - - - - - {this.renderConfirmButton()} + + + {this.props.showCopyOnSave && {this.renderCopyOnSave()}} + + + + + + {this.renderConfirmButton()} + ); @@ -351,10 +354,6 @@ export class SavedObjectSaveModal extends React.Component }; private renderCopyOnSave = () => { - if (!this.props.showCopyOnSave) { - return; - } - return ( = { + default: { + columns: [ + { + id: 'col-0-1', + meta: { + dimensionName: 'Slice size', + type: 'number', + }, + name: 'Field 1', + }, + { + id: 'col-0-2', + meta: { + dimensionName: 'Slice', + type: 'number', + }, + name: 'Field 2', + }, + ], + rows: [ + { + 'col-0-1': 0, + 'col-0-2': 0, + 'col-0-3': 0, + 'col-0-4': 0, + }, + ], + type: 'datatable', + }, +}; diff --git a/src/plugins/unified_histogram/public/__mocks__/services.ts b/src/plugins/unified_histogram/public/__mocks__/services.tsx similarity index 92% rename from src/plugins/unified_histogram/public/__mocks__/services.ts rename to src/plugins/unified_histogram/public/__mocks__/services.tsx index 71058be4f634f..6b6e5a7f46864 100644 --- a/src/plugins/unified_histogram/public/__mocks__/services.ts +++ b/src/plugins/unified_histogram/public/__mocks__/services.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import React from 'react'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; @@ -33,6 +33,7 @@ export const unifiedHistogramServicesMock = { suggestions: jest.fn(() => allSuggestionsMock), }; }), + EditLensConfigPanelApi: jest.fn().mockResolvedValue(Lens Config Panel Component), }, storage: { get: jest.fn(), diff --git a/src/plugins/unified_histogram/public/chart/chart.test.tsx b/src/plugins/unified_histogram/public/chart/chart.test.tsx index a2c5f3b195a21..b32979cc004e5 100644 --- a/src/plugins/unified_histogram/public/chart/chart.test.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.test.tsx @@ -233,6 +233,17 @@ describe('Chart', () => { expect(component.find(SuggestionSelector).exists()).toBeTruthy(); }); + it('should render the edit on the fly button when chart is visible and suggestions exist', async () => { + const component = await mountComponent({ + currentSuggestion: currentSuggestionMock, + allSuggestions: allSuggestionsMock, + isPlainRecord: true, + }); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeTruthy(); + }); + it('should render the save button when chart is visible and suggestions exist', async () => { const component = await mountComponent({ currentSuggestion: currentSuggestionMock, diff --git a/src/plugins/unified_histogram/public/chart/chart.tsx b/src/plugins/unified_histogram/public/chart/chart.tsx index f99d9b3de1cea..6b112f3578df9 100644 --- a/src/plugins/unified_histogram/public/chart/chart.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.tsx @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import { ReactElement, useMemo, useState } from 'react'; -import React, { memo } from 'react'; +import React, { ReactElement, useMemo, useState, useEffect, useCallback, memo } from 'react'; import { EuiButtonIcon, EuiContextMenu, @@ -18,6 +17,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { Suggestion } from '@kbn/lens-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/common'; import { DataView, DataViewField, DataViewType } from '@kbn/data-views-plugin/public'; import type { LensEmbeddableInput } from '@kbn/lens-plugin/public'; import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; @@ -42,6 +42,7 @@ import { useTotalHits } from './hooks/use_total_hits'; import { useRequestParams } from './hooks/use_request_params'; import { useChartStyles } from './hooks/use_chart_styles'; import { useChartActions } from './hooks/use_chart_actions'; +import { useChartConfigPanel } from './hooks/use_chart_config_panel'; import { getLensAttributes } from './utils/get_lens_attributes'; import { useRefetch } from './hooks/use_refetch'; import { useEditVisualization } from './hooks/use_edit_visualization'; @@ -67,6 +68,7 @@ export interface ChartProps { disableTriggers?: LensEmbeddableInput['disableTriggers']; disabledActions?: LensEmbeddableInput['disabledActions']; input$?: UnifiedHistogramInput$; + lensTablesAdapter?: Record; onResetChartHeight?: () => void; onChartHiddenChange?: (chartHidden: boolean) => void; onTimeIntervalChange?: (timeInterval: string) => void; @@ -101,6 +103,7 @@ export function Chart({ disableTriggers, disabledActions, input$: originalInput$, + lensTablesAdapter, onResetChartHeight, onChartHiddenChange, onTimeIntervalChange, @@ -112,6 +115,7 @@ export function Chart({ onBrushEnd, }: ChartProps) { const [isSaveModalVisible, setIsSaveModalVisible] = useState(false); + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const { showChartOptionsPopover, chartRef, @@ -190,6 +194,7 @@ export function Chart({ histogramCss, breakdownFieldSelectorGroupCss, breakdownFieldSelectorItemCss, + suggestionsSelectorItemCss, chartToolButtonCss, } = useChartStyles(chartVisible); @@ -215,6 +220,34 @@ export function Chart({ ] ); + const ChartConfigPanel = useChartConfigPanel({ + services, + lensAttributesContext, + dataView, + lensTablesAdapter, + currentSuggestion, + isFlyoutVisible, + setIsFlyoutVisible, + isPlainRecord, + query: originalQuery, + onSuggestionChange, + }); + + const onSuggestionSelectorChange = useCallback( + (s: Suggestion | undefined) => { + onSuggestionChange?.(s); + }, + [onSuggestionChange] + ); + + useEffect(() => { + // close the flyout for dataview mode + // or if no chart is visible + if (!chartVisible && isFlyoutVisible) { + setIsFlyoutVisible(false); + } + }, [chartVisible, isFlyoutVisible]); + const onEditVisualization = useEditVisualization({ services, dataView, @@ -226,6 +259,24 @@ export function Chart({ const canSaveVisualization = chartVisible && currentSuggestion && services.capabilities.dashboard?.showWriteControls; + const renderEditButton = useMemo( + () => ( + setIsFlyoutVisible(true)} + data-test-subj="unifiedHistogramEditFlyoutVisualization" + aria-label={i18n.translate('unifiedHistogram.editVisualizationButton', { + defaultMessage: 'Edit visualization', + })} + disabled={isFlyoutVisible} + /> + ), + [isFlyoutVisible] + ); + + const canEditVisualizationOnTheFly = isPlainRecord && chartVisible; + return ( )} {chartVisible && currentSuggestion && allSuggestions && allSuggestions?.length > 1 && ( - + )} @@ -296,6 +348,21 @@ export function Chart({ )} + {canEditVisualizationOnTheFly && ( + + {!isFlyoutVisible ? ( + + {renderEditButton} + + ) : ( + renderEditButton + )} + + )} {onEditVisualization && ( )} + {isFlyoutVisible && ChartConfigPanel} ); } diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index 61320c627bb13..63a0c9897b8c7 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -146,6 +146,7 @@ export function Histogram({ const chartCss = css` position: relative; flex-grow: 1; + margin-block: ${euiTheme.size.xs}; & > div { height: 100%; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.test.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.test.tsx new file mode 100644 index 0000000000000..5f0d4b17e80db --- /dev/null +++ b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.test.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { renderHook } from '@testing-library/react-hooks'; +import { act } from 'react-test-renderer'; +import { setTimeout } from 'timers/promises'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter'; +import { useChartConfigPanel } from './use_chart_config_panel'; +import type { LensAttributesContext } from '../utils/get_lens_attributes'; + +describe('useChartConfigPanel', () => { + it('should return a jsx element to edit the visualization', async () => { + const lensAttributes = { + visualizationType: 'lnsXY', + title: 'test', + } as TypedLensByValueInput['attributes']; + const hook = renderHook(() => + useChartConfigPanel({ + services: unifiedHistogramServicesMock, + dataView: dataViewWithTimefieldMock, + lensAttributesContext: { + attributes: lensAttributes, + } as unknown as LensAttributesContext, + isFlyoutVisible: true, + setIsFlyoutVisible: jest.fn(), + isPlainRecord: true, + lensTablesAdapter: lensTablesAdapterMock, + query: { + sql: 'Select * from test', + }, + }) + ); + await act(() => setTimeout(0)); + expect(hook.result.current).toBeDefined(); + expect(hook.result.current).not.toBeNull(); + }); + + it('should return null if not in text based mode', async () => { + const lensAttributes = { + visualizationType: 'lnsXY', + title: 'test', + } as TypedLensByValueInput['attributes']; + const hook = renderHook(() => + useChartConfigPanel({ + services: unifiedHistogramServicesMock, + dataView: dataViewWithTimefieldMock, + lensAttributesContext: { + attributes: lensAttributes, + } as unknown as LensAttributesContext, + isFlyoutVisible: true, + setIsFlyoutVisible: jest.fn(), + isPlainRecord: false, + }) + ); + await act(() => setTimeout(0)); + expect(hook.result.current).toBeNull(); + }); +}); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx new file mode 100644 index 0000000000000..99fb8fbb8fe31 --- /dev/null +++ b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 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, useEffect, useRef, useState } from 'react'; +import type { AggregateQuery, Query } from '@kbn/es-query'; +import { isEqual } from 'lodash'; +import type { Suggestion } from '@kbn/lens-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/common'; + +import type { UnifiedHistogramServices } from '../../types'; +import type { LensAttributesContext } from '../utils/get_lens_attributes'; + +export function useChartConfigPanel({ + services, + lensAttributesContext, + dataView, + lensTablesAdapter, + currentSuggestion, + isFlyoutVisible, + setIsFlyoutVisible, + isPlainRecord, + query, + onSuggestionChange, +}: { + services: UnifiedHistogramServices; + lensAttributesContext: LensAttributesContext; + dataView: DataView; + isFlyoutVisible: boolean; + setIsFlyoutVisible: (flag: boolean) => void; + lensTablesAdapter?: Record; + currentSuggestion?: Suggestion; + isPlainRecord?: boolean; + query?: Query | AggregateQuery; + onSuggestionChange?: (suggestion: Suggestion | undefined) => void; +}) { + const [editLensConfigPanel, setEditLensConfigPanel] = useState(null); + const previousSuggestion = useRef(undefined); + const previousAdapters = useRef | undefined>(undefined); + const previousQuery = useRef(undefined); + const updateSuggestion = useCallback( + (datasourceState, visualizationState) => { + const updatedSuggestion = { + ...currentSuggestion, + ...(datasourceState && { datasourceState }), + ...(visualizationState && { visualizationState }), + } as Suggestion; + onSuggestionChange?.(updatedSuggestion); + }, + [currentSuggestion, onSuggestionChange] + ); + + useEffect(() => { + const dataHasChanged = + Boolean(lensTablesAdapter) && + !isEqual(previousAdapters.current, lensTablesAdapter) && + query !== previousQuery?.current; + async function fetchLensConfigComponent() { + const Component = await services.lens.EditLensConfigPanelApi(); + const panel = ( + + ); + setEditLensConfigPanel(panel); + previousSuggestion.current = currentSuggestion; + previousAdapters.current = lensTablesAdapter; + if (dataHasChanged) { + previousQuery.current = query; + } + } + const suggestionHasChanged = currentSuggestion?.title !== previousSuggestion?.current?.title; + // rerender the component if the data has changed or the suggestion + // as I can have different suggestions for the same data + if (isPlainRecord && (dataHasChanged || suggestionHasChanged || !isFlyoutVisible)) { + fetchLensConfigComponent(); + } + }, [ + lensAttributesContext.attributes, + services.lens, + dataView, + updateSuggestion, + isPlainRecord, + currentSuggestion, + query, + isFlyoutVisible, + lensTablesAdapter, + setIsFlyoutVisible, + ]); + + return isPlainRecord ? editLensConfigPanel : null; +} diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_chart_styles.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_chart_styles.tsx index c019c7cef981e..13b527be702c1 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_chart_styles.tsx +++ b/src/plugins/unified_histogram/public/chart/hooks/use_chart_styles.tsx @@ -56,6 +56,11 @@ export const useChartStyles = (chartVisible: boolean) => { align-items: flex-end; padding-left: ${euiTheme.size.s}; `; + const suggestionsSelectorItemCss = css` + min-width: 0; + align-items: flex-start; + padding-left: ${euiTheme.size.s}; + `; const chartToolButtonCss = css` display: flex; justify-content: center; @@ -70,6 +75,7 @@ export const useChartStyles = (chartVisible: boolean) => { histogramCss, breakdownFieldSelectorGroupCss, breakdownFieldSelectorItemCss, + suggestionsSelectorItemCss, chartToolButtonCss, }; }; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts index 8bd9e9161c837..ec213dd53e141 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts @@ -81,6 +81,21 @@ describe('useEditVisualization', () => { expect(hook.result.current).toBeUndefined(); }); + it('should return undefined if is on text based mode', async () => { + getTriggerCompatibleActions.mockReturnValue(Promise.resolve([{ id: 'test' }])); + const hook = renderHook(() => + useEditVisualization({ + services: unifiedHistogramServicesMock, + dataView: dataViewWithTimefieldMock, + relativeTimeRange: { from: 'now-15m', to: 'now' }, + lensAttributes: {} as unknown as TypedLensByValueInput['attributes'], + isPlainRecord: true, + }) + ); + await act(() => setTimeout(0)); + expect(hook.result.current).toBeUndefined(); + }); + it('should return undefined if the time field is not visualizable', async () => { getTriggerCompatibleActions.mockReturnValue(Promise.resolve([{ id: 'test' }])); const dataView = { diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.ts b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.ts index e681fd34cd91e..b02732bfcbfc9 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.ts @@ -32,10 +32,10 @@ export const useEditVisualization = ({ const [canVisualize, setCanVisualize] = useState(false); const checkCanVisualize = useCallback(async () => { - if (!dataView.id) { + if (!dataView.id || isPlainRecord) { return false; } - if (!isPlainRecord && (!dataView.isTimeBased() || !dataView.getTimeField().visualizable)) { + if (!dataView.isTimeBased() || !dataView.getTimeField().visualizable) { return false; } diff --git a/src/plugins/unified_histogram/public/chart/suggestion_selector.tsx b/src/plugins/unified_histogram/public/chart/suggestion_selector.tsx index 85860cf2c9bc8..0196387633396 100644 --- a/src/plugins/unified_histogram/public/chart/suggestion_selector.tsx +++ b/src/plugins/unified_histogram/public/chart/suggestion_selector.tsx @@ -67,7 +67,7 @@ export const SuggestionSelector = ({ const { euiTheme } = useEuiTheme(); const suggestionComboCss = css` width: 100%; - max-width: ${euiTheme.base * 22}px; + max-width: ${euiTheme.base * 15}px; `; return ( @@ -78,9 +78,7 @@ export const SuggestionSelector = ({ > } placeholder={i18n.translate('unifiedHistogram.suggestionSelectorPlaceholder', { defaultMessage: 'Select visualization', })} @@ -88,9 +86,9 @@ export const SuggestionSelector = ({ options={suggestionOptions} selectedOptions={selectedSuggestion} onChange={onSelectionChange} - compressed fullWidth={true} isClearable={false} + compressed onFocus={disableFieldPopover} onBlur={enableFieldPopover} renderOption={(option) => { diff --git a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts index fcdd194410db0..c0eeb9448eee7 100644 --- a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts +++ b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts @@ -15,6 +15,7 @@ import { UnifiedHistogramFetchStatus } from '../../types'; import { dataViewMock } from '../../__mocks__/data_view'; import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { currentSuggestionMock } from '../../__mocks__/suggestions'; +import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter'; import { unifiedHistogramServicesMock } from '../../__mocks__/services'; import { createStateService, @@ -28,6 +29,7 @@ describe('useStateProps', () => { breakdownField: 'bytes', chartHidden: false, lensRequestAdapter: new RequestAdapter(), + lensTablesAdapter: lensTablesAdapterMock, timeInterval: 'auto', topPanelHeight: 100, totalHitsStatus: UnifiedHistogramFetchStatus.uninitialized, @@ -82,6 +84,37 @@ describe('useStateProps', () => { "total": undefined, }, "isPlainRecord": false, + "lensTablesAdapter": Object { + "default": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + }, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -126,6 +159,37 @@ describe('useStateProps', () => { "total": undefined, }, "isPlainRecord": true, + "lensTablesAdapter": Object { + "default": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + }, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -191,6 +255,37 @@ describe('useStateProps', () => { "total": undefined, }, "isPlainRecord": false, + "lensTablesAdapter": Object { + "default": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + }, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -232,6 +327,37 @@ describe('useStateProps', () => { "total": undefined, }, "isPlainRecord": false, + "lensTablesAdapter": Object { + "default": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + }, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], diff --git a/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts b/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts index b9c4570cdecb9..a5845731cf12e 100644 --- a/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts +++ b/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts @@ -23,6 +23,7 @@ import { timeIntervalSelector, totalHitsResultSelector, totalHitsStatusSelector, + lensTablesAdapterSelector, } from '../utils/state_selectors'; import { useStateSelector } from '../utils/use_state_selector'; @@ -44,7 +45,7 @@ export const useStateProps = ({ const timeInterval = useStateSelector(stateService?.state$, timeIntervalSelector); const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector); const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector); - + const lensTablesAdapter = useStateSelector(stateService?.state$, lensTablesAdapterSelector); /** * Contexts */ @@ -139,6 +140,7 @@ export const useStateProps = ({ (event: UnifiedHistogramChartLoadEvent) => { // We need to store the Lens request adapter in order to inspect its requests stateService?.setLensRequestAdapter(event.adapters.requests); + stateService?.setLensTablesAdapter(event.adapters.tables?.tables); }, [stateService] ); @@ -174,6 +176,7 @@ export const useStateProps = ({ breakdown, request, isPlainRecord, + lensTablesAdapter, onTopPanelHeightChange, onTimeIntervalChange, onTotalHitsChange, diff --git a/src/plugins/unified_histogram/public/container/services/state_service.test.ts b/src/plugins/unified_histogram/public/container/services/state_service.test.ts index eb839e6eaba0f..eb7232e889037 100644 --- a/src/plugins/unified_histogram/public/container/services/state_service.test.ts +++ b/src/plugins/unified_histogram/public/container/services/state_service.test.ts @@ -9,6 +9,7 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { UnifiedHistogramFetchStatus } from '../..'; import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter'; import { getChartHidden, getTopPanelHeight, @@ -46,6 +47,7 @@ describe('UnifiedHistogramStateService', () => { breakdownField: 'bytes', chartHidden: false, lensRequestAdapter: new RequestAdapter(), + lensTablesAdapter: lensTablesAdapterMock, timeInterval: 'auto', topPanelHeight: 100, totalHitsStatus: UnifiedHistogramFetchStatus.uninitialized, @@ -134,6 +136,8 @@ describe('UnifiedHistogramStateService', () => { expect(state).toEqual(newState); stateService.setLensRequestAdapter(undefined); newState = { ...newState, lensRequestAdapter: undefined }; + stateService.setLensTablesAdapter(undefined); + newState = { ...newState, lensTablesAdapter: undefined }; expect(state).toEqual(newState); stateService.setTotalHits({ totalHitsStatus: UnifiedHistogramFetchStatus.complete, diff --git a/src/plugins/unified_histogram/public/container/services/state_service.ts b/src/plugins/unified_histogram/public/container/services/state_service.ts index 01d1a7f0c3f60..1fd0905d86c7a 100644 --- a/src/plugins/unified_histogram/public/container/services/state_service.ts +++ b/src/plugins/unified_histogram/public/container/services/state_service.ts @@ -8,6 +8,7 @@ import type { RequestAdapter } from '@kbn/inspector-plugin/common'; import type { Suggestion } from '@kbn/lens-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/common'; import { BehaviorSubject, Observable } from 'rxjs'; import { UnifiedHistogramFetchStatus } from '../..'; import type { UnifiedHistogramServices } from '../../types'; @@ -40,6 +41,10 @@ export interface UnifiedHistogramState { * The current Lens request adapter */ lensRequestAdapter: RequestAdapter | undefined; + /** + * The current Lens request table + */ + lensTablesAdapter?: Record; /** * The current time interval of the chart */ @@ -108,6 +113,10 @@ export interface UnifiedHistogramStateService { * Sets the current Lens request adapter */ setLensRequestAdapter: (lensRequestAdapter: RequestAdapter | undefined) => void; + /** + * Sets the current Lens tables + */ + setLensTablesAdapter: (lensTablesAdapter: Record | undefined) => void; /** * Sets the current total hits status and result */ @@ -190,6 +199,10 @@ export const createStateService = ( updateState({ lensRequestAdapter }); }, + setLensTablesAdapter: (lensTablesAdapter: Record | undefined) => { + updateState({ lensTablesAdapter }); + }, + setTotalHits: (totalHits: { totalHitsStatus: UnifiedHistogramFetchStatus; totalHitsResult: number | Error | undefined; diff --git a/src/plugins/unified_histogram/public/container/utils/state_selectors.ts b/src/plugins/unified_histogram/public/container/utils/state_selectors.ts index d11fb1182cc45..80e809f4fc38f 100644 --- a/src/plugins/unified_histogram/public/container/utils/state_selectors.ts +++ b/src/plugins/unified_histogram/public/container/utils/state_selectors.ts @@ -15,3 +15,4 @@ export const topPanelHeightSelector = (state: UnifiedHistogramState) => state.to export const totalHitsResultSelector = (state: UnifiedHistogramState) => state.totalHitsResult; export const totalHitsStatusSelector = (state: UnifiedHistogramState) => state.totalHitsStatus; export const currentSuggestionSelector = (state: UnifiedHistogramState) => state.currentSuggestion; +export const lensTablesAdapterSelector = (state: UnifiedHistogramState) => state.lensTablesAdapter; diff --git a/src/plugins/unified_histogram/public/layout/layout.tsx b/src/plugins/unified_histogram/public/layout/layout.tsx index 3c5b021e0b08d..cd6d2ffb5ec07 100644 --- a/src/plugins/unified_histogram/public/layout/layout.tsx +++ b/src/plugins/unified_histogram/public/layout/layout.tsx @@ -11,6 +11,7 @@ import { PropsWithChildren, ReactElement, RefObject } from 'react'; import React, { useMemo } from 'react'; import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; import { css } from '@emotion/css'; +import type { Datatable } from '@kbn/expressions-plugin/common'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import type { LensEmbeddableInput, LensSuggestionsApi, Suggestion } from '@kbn/lens-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; @@ -77,6 +78,7 @@ export interface UnifiedHistogramLayoutProps extends PropsWithChildren * Context object for the hits count -- leave undefined to hide the hits count */ hits?: UnifiedHistogramHitsContext; + lensTablesAdapter?: Record; /** * Context object for the chart -- leave undefined to hide the chart */ @@ -169,6 +171,7 @@ export const UnifiedHistogramLayout = ({ columns, request, hits, + lensTablesAdapter, chart: originalChart, breakdown, resizeRef, @@ -273,6 +276,7 @@ export const UnifiedHistogramLayout = ({ onChartLoad={onChartLoad} onFilter={onFilter} onBrushEnd={onBrushEnd} + lensTablesAdapter={lensTablesAdapter} /> {children} diff --git a/test/api_integration/apis/data_views/fields_api/update_fields/main.ts b/test/api_integration/apis/data_views/fields_api/update_fields/main.ts index d48dd90396e16..40c03bbec46c1 100644 --- a/test/api_integration/apis/data_views/fields_api/update_fields/main.ts +++ b/test/api_integration/apis/data_views/fields_api/update_fields/main.ts @@ -437,6 +437,7 @@ export default function ({ getService }: FtrProviderContext) { const title = indexPattern.title; await supertest.delete(`${config.path}/${indexPattern.id}`); const response1 = await supertest.post(config.path).send({ + override: true, [config.serviceKey]: { title, fields: { diff --git a/test/functional/apps/discover/group1/_shared_links.ts b/test/functional/apps/discover/group1/_shared_links.ts index 11e92297aad99..30626d9d42576 100644 --- a/test/functional/apps/discover/group1/_shared_links.ts +++ b/test/functional/apps/discover/group1/_shared_links.ts @@ -8,6 +8,7 @@ import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import expect from '@kbn/expect'; +import { decompressFromBase64 } from 'lz-string'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -21,8 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const toasts = getService('toasts'); const deployment = getService('deployment'); - // Failing: See https://github.com/elastic/kibana/issues/158465 - describe.skip('shared links', function describeIndexTests() { + describe('shared links', function describeIndexTests() { let baseUrl: string; async function setup({ storeStateInSessionStorage }: { storeStateInSessionStorage: boolean }) { @@ -75,14 +75,29 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('permalink', function () { it('should allow for copying the snapshot URL', async function () { - const lz = - 'N4IgjgrgpgTgniAXKSsGJCANCANgQwDsBzCfYqJEAa2nhAF8cBnAexgBckBtbkAAQ4BLALZRmHfCIAO2EABNxAYxABdVTiWtcEEYWY8N' + - 'IIYUUAPKrlbEJ%2BZgAsAtACo5JjrABu%2BXFXwQOVjkAMyFcDxgDRG4jeXxJADUhKAB3AEl5S2tbBxc5YTEAJSIKJFBgmFYRKgAmAAY' + - 'ARgBWRzqATkcGtoAVOoA2RABmBsQAFlGAOjrpgC18oIx65taOmsHuhoAOIZHxqdnGHBgoCvF7NMII719kEGvoJD7p6Zxpf2ZKRA4YaAY' + - 'GIA%3D'; const actualUrl = await PageObjects.share.getSharedUrl(); expect(actualUrl).to.contain(`?l=${DISCOVER_APP_LOCATOR}`); - expect(actualUrl).to.contain(`&lz=${lz}`); + const urlSearchParams = new URLSearchParams(actualUrl); + expect(JSON.parse(decompressFromBase64(urlSearchParams.get('lz')!)!)).to.eql({ + query: { + language: 'kuery', + query: '', + }, + sort: [['@timestamp', 'desc']], + columns: [], + index: 'logstash-*', + interval: 'auto', + filters: [], + dataViewId: 'logstash-*', + timeRange: { + from: '2015-09-19T06:31:44.000Z', + to: '2015-09-23T18:31:44.000Z', + }, + refreshInterval: { + value: 60000, + pause: true, + }, + }); }); it('should allow for copying the snapshot URL as a short URL', async function () { diff --git a/test/functional/apps/discover/group3/_request_counts.ts b/test/functional/apps/discover/group3/_request_counts.ts index c4f5676f357e5..24698bcb45c11 100644 --- a/test/functional/apps/discover/group3/_request_counts.ts +++ b/test/functional/apps/discover/group3/_request_counts.ts @@ -27,7 +27,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const queryBar = getService('queryBar'); const elasticChart = getService('elasticChart'); - describe('discover request counts', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/161157 + describe.skip('discover request counts', function describeIndexTests() { before(async function () { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/long_window_logstash'); diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 9de8ba0d569f0..393f093d54189 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -12,6 +12,7 @@ import { Key, Origin, WebDriver } from 'selenium-webdriver'; import { modifyUrl } from '@kbn/std'; import sharp from 'sharp'; +import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import { WebElementWrapper } from '../lib/web_element_wrapper'; import { FtrProviderContext, FtrService } from '../../ftr_provider_context'; import { Browsers } from '../remote/browsers'; @@ -632,8 +633,33 @@ class BrowserService extends FtrService { return Boolean(result?.state === 'granted'); } - public getClipboardValue(): Promise { - return this.driver.executeAsyncScript('navigator.clipboard.readText().then(arguments[0])'); + public async getClipboardValue(): Promise { + return await this.driver.executeAsyncScript( + 'navigator.clipboard.readText().then(arguments[0])' + ); + } + + /** + * Checks if browser session is active and any browser window exists + * @returns {Promise} + */ + public async hasOpenWindow(): Promise { + if (this.driver == null) { + return false; + } else { + try { + const windowHandles = await this.driver.getAllWindowHandles(); + return windowHandles.length > 0; + } catch (err) { + if (err instanceof NoSuchSessionError) { + // https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/InvalidSessionID + this.log.error( + `WebDriver session is no longer valid.\nProbably Chrome process crashed when it tried to use more memory than what was available.` + ); + } + return false; + } + } } } diff --git a/test/functional/services/common/failure_debugging.ts b/test/functional/services/common/failure_debugging.ts index 5555ae78bccf8..f269a83359da0 100644 --- a/test/functional/services/common/failure_debugging.ts +++ b/test/functional/services/common/failure_debugging.ts @@ -50,7 +50,12 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex async function onFailure(_: any, test: Test) { const name = FtrScreenshotFilename.create(test.fullTitle(), { ext: false }); - await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); + const hasOpenWindow = await browser.hasOpenWindow(); + if (hasOpenWindow) { + await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); + } else { + log.error('Browser is closed, no artifacts were captured for the failure'); + } } lifecycle.testFailure.add(onFailure); diff --git a/test/functional/services/common/screenshots.ts b/test/functional/services/common/screenshots.ts index 191fc5202c417..50421e480dd9c 100644 --- a/test/functional/services/common/screenshots.ts +++ b/test/functional/services/common/screenshots.ts @@ -9,7 +9,6 @@ import { resolve, dirname } from 'path'; import { writeFile, readFileSync, mkdir } from 'fs'; import { promisify } from 'util'; -import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import del from 'del'; @@ -85,20 +84,9 @@ export class ScreenshotsService extends FtrService { } private async capture(path: string, el?: WebElementWrapper) { - try { - this.log.info(`Taking screenshot "${path}"`); - const screenshot = await (el ? el.takeScreenshot() : this.browser.takeScreenshot()); - await mkdirAsync(dirname(path), { recursive: true }); - await writeFileAsync(path, screenshot, 'base64'); - } catch (err) { - this.log.error('SCREENSHOT FAILED'); - this.log.error(err); - if (err instanceof NoSuchSessionError) { - // https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/InvalidSessionID - this.log.error( - `WebDriver session is no longer valid.\nProbably Chrome process crashed when it tried to use more memory than what was available.` - ); - } - } + this.log.info(`Taking ${el ? 'element' : 'window'} screenshot "${path}"`); + const screenshot = await (el ? el.takeScreenshot() : this.browser.takeScreenshot()); + await mkdirAsync(dirname(path), { recursive: true }); + await writeFileAsync(path, screenshot, 'base64'); } } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 284029514606f..b3038130b0187 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import { FtrProviderContext } from '../../ftr_provider_context'; import { initWebDriver, BrowserConfig } from './webdriver'; import { Browsers } from './browsers'; @@ -27,6 +28,21 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } }; + const tryWebDriverCall = async (command: () => Promise) => { + // Since WebDriver session may be deleted, we fail silently. Use only in after hooks. + try { + await command(); + } catch (error) { + if (error instanceof NoSuchSessionError) { + // Avoid duplicating NoSuchSessionError error output on each hook + // https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/InvalidSessionID + log.error('WebDriver session is no longer valid'); + } else { + throw error; + } + } + }; + const browserConfig: BrowserConfig = { logPollingMs: config.get('browser.logPollingMs'), acceptInsecureCerts: config.get('browser.acceptInsecureCerts'), @@ -72,14 +88,18 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }); lifecycle.afterTestSuite.add(async () => { - const { width, height } = windowSizeStack.shift()!; - await driver.manage().window().setRect({ width, height }); - await clearBrowserStorage('sessionStorage'); - await clearBrowserStorage('localStorage'); + await tryWebDriverCall(async () => { + const { width, height } = windowSizeStack.shift()!; + await driver.manage().window().setRect({ width, height }); + await clearBrowserStorage('sessionStorage'); + await clearBrowserStorage('localStorage'); + }); }); lifecycle.cleanup.add(async () => { - await driver.quit(); + await tryWebDriverCall(async () => { + await driver.quit(); + }); }); return { driver, browserType, consoleLog$ }; diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index c4f5cf5a69568..8ec34f1b91ec4 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -258,6 +258,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.security.showNavLinks (boolean)', 'xpack.security.ui (any)', 'xpack.spaces.maxSpaces (number)', + 'xpack.spaces.allowFeatureVisibility (any)', 'xpack.securitySolution.enableExperimental (array)', 'xpack.securitySolution.prebuiltRulesPackageVersion (string)', 'xpack.snapshot_restore.slm_ui.enabled (boolean)', diff --git a/tsconfig.base.json b/tsconfig.base.json index 6db4864f58f05..1f70f84715646 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -930,6 +930,8 @@ "@kbn/logging/*": ["packages/kbn-logging/*"], "@kbn/logging-mocks": ["packages/kbn-logging-mocks"], "@kbn/logging-mocks/*": ["packages/kbn-logging-mocks/*"], + "@kbn/logs-shared-plugin": ["x-pack/plugins/logs_shared"], + "@kbn/logs-shared-plugin/*": ["x-pack/plugins/logs_shared/*"], "@kbn/logstash-plugin": ["x-pack/plugins/logstash"], "@kbn/logstash-plugin/*": ["x-pack/plugins/logstash/*"], "@kbn/managed-vscode-config": ["packages/kbn-managed-vscode-config"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index eea7dced5e278..2fa461531b499 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -37,6 +37,7 @@ "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", + "xpack.logsShared": "plugins/logs_shared", "xpack.fleet": "plugins/fleet", "xpack.ingestPipelines": "plugins/ingest_pipelines", "xpack.kubernetesSecurity": "plugins/kubernetes_security", diff --git a/x-pack/performance/journeys/cloud_security_dashboard.ts b/x-pack/performance/journeys/cloud_security_dashboard.ts index 39625126e7067..a7256f44717f2 100644 --- a/x-pack/performance/journeys/cloud_security_dashboard.ts +++ b/x-pack/performance/journeys/cloud_security_dashboard.ts @@ -14,6 +14,7 @@ export const journey = new Journey({ const response = await kibanaServer.request({ path: '/internal/cloud_security_posture/status?check=init', method: 'GET', + headers: { 'elastic-api-version': '1' }, }); expect(response.status).to.eql(200); expect(response.data).to.eql({ isPluginInitialized: true }); diff --git a/x-pack/plugins/apm/common/utils/formatters/formatters.test.ts b/x-pack/plugins/apm/common/utils/formatters/formatters.test.ts index f876b639c923d..b04a746fa5437 100644 --- a/x-pack/plugins/apm/common/utils/formatters/formatters.test.ts +++ b/x-pack/plugins/apm/common/utils/formatters/formatters.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { asPercent, asDecimalOrInteger } from './formatters'; +import { asPercent, asDecimalOrInteger, asBigNumber } from './formatters'; describe('formatters', () => { describe('asPercent', () => { @@ -73,3 +73,52 @@ describe('formatters', () => { }); }); }); + +describe('asBigNumber', () => { + [ + { + input: 0, + output: '0', + }, + { + input: 999, + output: '999', + }, + { + input: 999.999, + output: '1,000', + }, + { + input: 449900, + output: '450k', + }, + { + input: 450000, + output: '450k', + }, + { + input: 450010, + output: '450k', + }, + { + input: 2.4991e7, + output: '25m', + }, + { + input: 9e9, + output: '9b', + }, + { + input: 1e12, + output: '1t', + }, + { + input: 1e15, + output: '1,000t', + }, + ].forEach(({ input, output }) => { + it(`${input} becomes ${output}`, () => { + expect(asBigNumber(input)).toBe(output); + }); + }); +}); diff --git a/x-pack/plugins/apm/common/utils/formatters/formatters.ts b/x-pack/plugins/apm/common/utils/formatters/formatters.ts index 67a259caa2534..5093f22ee7542 100644 --- a/x-pack/plugins/apm/common/utils/formatters/formatters.ts +++ b/x-pack/plugins/apm/common/utils/formatters/formatters.ts @@ -62,3 +62,23 @@ export function asDecimalOrInteger(value: number, threshold = 10) { } return asDecimal(value); } + +export function asBigNumber(value: number): string { + if (value < 1e3) { + return asInteger(value); + } + + if (value < 1e6) { + return `${asInteger(value / 1e3)}k`; + } + + if (value < 1e9) { + return `${asInteger(value / 1e6)}m`; + } + + if (value < 1e12) { + return `${asInteger(value / 1e9)}b`; + } + + return `${asInteger(value / 1e12)}t`; +} diff --git a/x-pack/plugins/apm/kibana.jsonc b/x-pack/plugins/apm/kibana.jsonc index ac33a0ee0b844..0b248bbbe53f6 100644 --- a/x-pack/plugins/apm/kibana.jsonc +++ b/x-pack/plugins/apm/kibana.jsonc @@ -15,6 +15,7 @@ "embeddable", "features", "infra", + "logsShared", "inspector", "licensing", "observability", diff --git a/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx b/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx index d5cd8c530332e..7e4914f29a671 100644 --- a/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { ObservabilityAlertSearchBar } from '@kbn/observability-plugin/public'; import { AlertStatus } from '@kbn/observability-plugin/common/typings'; @@ -72,6 +72,11 @@ export function AlertsOverview() { ]; }, [serviceName, environment]); + const onKueryChange = useCallback( + (value) => push(history, { query: { kuery: value } }), + [history] + ); + return ( @@ -86,9 +91,7 @@ export function AlertsOverview() { onRangeToChange={(value) => push(history, { query: { rangeTo: value } }) } - onKueryChange={(value) => - push(history, { query: { kuery: value } }) - } + onKueryChange={onKueryChange} defaultSearchQueries={apmQueries} onStatusChange={setAlertStatusFilter} onEsQueryChange={setEsQuery} diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx index 9fcb9c10e148f..d4776c8995c43 100644 --- a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx @@ -10,26 +10,20 @@ import { EuiBasicTable, EuiBasicTableColumn, EuiSpacer, + EuiText, + EuiToolTip, } from '@elastic/eui'; import React, { useState, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { orderBy } from 'lodash'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { asInteger } from '../../../../common/utils/formatters'; +import { asBigNumber, asInteger } from '../../../../common/utils/formatters'; import { APM_STATIC_DATA_VIEW_ID } from '../../../../common/data_view_constants'; import type { ApmEvent } from '../../../../server/routes/diagnostics/bundle/get_apm_events'; import { useDiagnosticsContext } from './context/use_diagnostics'; import { ApmPluginStartDeps } from '../../../plugin'; import { SearchBar } from '../../shared/search_bar/search_bar'; -function formatDocCount(count?: number) { - if (count === undefined) { - return '-'; - } - - return asInteger(count); -} - export function DiagnosticsApmDocuments() { const { diagnosticsBundle, isImported } = useDiagnosticsContext(); const { discover } = useKibana().services; @@ -46,7 +40,9 @@ export function DiagnosticsApmDocuments() { legacy === true && docCount === 0 && intervals && - Object.values(intervals).every((interval) => interval === 0); + Object.values(intervals).every( + (interval) => interval.eventDocCount === 0 + ); return !isLegacyAndUnused; }) ?? [] @@ -57,36 +53,40 @@ export function DiagnosticsApmDocuments() { { name: 'Name', field: 'name', - width: '40%', + width: '30%', }, { name: 'Doc count', field: 'docCount', - render: (_, { docCount }) => asInteger(docCount), + render: (_, { docCount }) => ( + +
{asBigNumber(docCount)}
+
+ ), sortable: true, }, { name: '1m', field: 'intervals.1m', render: (_, { intervals }) => { - const docCount = intervals?.['1m']; - return formatDocCount(docCount); + const interval = intervals?.['1m']; + return ; }, }, { name: '10m', field: 'intervals.10m', render: (_, { intervals }) => { - const docCount = intervals?.['10m']; - return formatDocCount(docCount); + const interval = intervals?.['10m']; + return ; }, }, { name: '60m', field: 'intervals.60m', render: (_, { intervals }) => { - const docCount = intervals?.['60m']; - return formatDocCount(docCount); + const interval = intervals?.['60m']; + return ; }, }, { @@ -159,3 +159,33 @@ export function DiagnosticsApmDocuments() { ); } + +function IntervalDocCount({ + interval, +}: { + interval?: { + metricDocCount: number; + eventDocCount: number; + }; +}) { + if (interval === undefined) { + return <>-; + } + + return ( + +
+ {asBigNumber(interval.metricDocCount)}  + + ({asBigNumber(interval.eventDocCount)} events) + +
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index 83cf09b3e586e..bde1749f33d35 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import moment from 'moment'; -import { LogStream } from '@kbn/infra-plugin/public'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx index 7e363d50ae666..78e6a89d30620 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx @@ -7,7 +7,7 @@ import { EuiSpacer, EuiTab, EuiTabs, EuiSkeletonText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LogStream } from '@kbn/infra-plugin/public'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import React, { useMemo } from 'react'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { TransactionMetadata } from '../../../shared/metadata_table/transaction_metadata'; diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 6400365aa87a1..a2ab809092055 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -401,5 +401,6 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { prepend, append, isSelected: key === selectedTab, + 'data-test-subj': `${key}Tab`, })); } diff --git a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx index 2f92aa4b18f4c..496010a14853a 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx @@ -222,5 +222,6 @@ function useTabs({ selectedTabKey }: { selectedTabKey: Tab['key'] }) { label, append, isSelected: key === selectedTabKey, + 'data-test-subj': `${key}Tab`, })); } diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts index 5c700d1b24945..b1dd52e630783 100644 --- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts +++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts @@ -13,6 +13,7 @@ import { METRICSET_NAME, METRICSET_INTERVAL, TRANSACTION_DURATION_SUMMARY, + INDEX, } from '../../../../common/es_fields/apm'; import { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; import { getTypedSearch, TypedSearch } from '../create_typed_es_client'; @@ -24,7 +25,7 @@ export interface ApmEvent { kuery: string; index: string[]; docCount: number; - intervals?: Record; + intervals?: Record; } export async function getApmEvents({ @@ -93,19 +94,19 @@ export async function getApmEvents({ }), getEventWithMetricsetInterval({ ...commonProps, - name: 'Metric: Span breakdown', + name: 'Metric: Service summary', index: getApmIndexPatterns([apmIndices.metric]), kuery: mergeKueries( - `${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "span_breakdown"`, + `${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "service_summary"`, kuery ), }), - getEventWithMetricsetInterval({ + getEvent({ ...commonProps, - name: 'Metric: Service summary', + name: 'Metric: Span breakdown', index: getApmIndexPatterns([apmIndices.metric]), kuery: mergeKueries( - `${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "service_summary"`, + `${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "span_breakdown"`, kuery ), }), @@ -165,20 +166,33 @@ async function getEventWithMetricsetInterval({ size: 1000, field: METRICSET_INTERVAL, }, + aggs: { + metric_doc_count: { + value_count: { + field: INDEX, + }, + }, + }, }, }, }); - const defaultIntervals = { '1m': 0, '10m': 0, '60m': 0 }; + const defaultIntervals = { + '1m': { metricDocCount: 0, eventDocCount: 0 }, + '10m': { metricDocCount: 0, eventDocCount: 0 }, + '60m': { metricDocCount: 0, eventDocCount: 0 }, + }; const foundIntervals = res.aggregations?.metricset_intervals.buckets.reduce< - Record + Record >((acc, item) => { - acc[item.key] = item.doc_count; + acc[item.key] = { + metricDocCount: item.metric_doc_count.value, + eventDocCount: item.doc_count, + }; return acc; }, {}); const intervals = merge(defaultIntervals, foundIntervals); - return { legacy, name, diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json index 4052693523612..6947c8cfcd628 100644 --- a/x-pack/plugins/apm/tsconfig.json +++ b/x-pack/plugins/apm/tsconfig.json @@ -92,6 +92,7 @@ "@kbn/dashboard-plugin", "@kbn/controls-plugin", "@kbn/core-http-server", + "@kbn/logs-shared-plugin", "@kbn/unified-field-list", "@kbn/slo-schema", "@kbn/discover-plugin" diff --git a/x-pack/plugins/cases/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts index fd734c943de0e..9778195465432 100644 --- a/x-pack/plugins/cases/common/api/cases/case.ts +++ b/x-pack/plugins/cases/common/api/cases/case.ts @@ -24,6 +24,7 @@ import { MAX_ASSIGNEES_FILTER_LENGTH, MAX_REPORTERS_FILTER_LENGTH, MAX_TAGS_FILTER_LENGTH, + MAX_BULK_GET_CASES, } from '../../constants'; export const AttachmentTotalsRt = rt.strict({ @@ -144,20 +145,24 @@ export const CasePostRequestRt = rt.intersection([ /** * Description of the case */ - description: limitedStringSchema('description', 1, MAX_DESCRIPTION_LENGTH), + description: limitedStringSchema({ + fieldName: 'description', + min: 1, + max: MAX_DESCRIPTION_LENGTH, + }), /** * Identifiers for the case. */ - tags: limitedArraySchema( - limitedStringSchema('tag', 1, MAX_LENGTH_PER_TAG), - 0, - MAX_TAGS_PER_CASE, - 'tags' - ), + tags: limitedArraySchema({ + codec: limitedStringSchema({ fieldName: 'tag', min: 1, max: MAX_LENGTH_PER_TAG }), + fieldName: 'tags', + min: 0, + max: MAX_TAGS_PER_CASE, + }), /** * Title of the case */ - title: limitedStringSchema('title', 1, MAX_TITLE_LENGTH), + title: limitedStringSchema({ fieldName: 'title', min: 1, max: MAX_TITLE_LENGTH }), /** * The external configuration for the case */ @@ -186,7 +191,10 @@ export const CasePostRequestRt = rt.intersection([ /** * The category of the case. */ - category: rt.union([limitedStringSchema('category', 1, MAX_CATEGORY_LENGTH), rt.null]), + category: rt.union([ + limitedStringSchema({ fieldName: 'category', min: 1, max: MAX_CATEGORY_LENGTH }), + rt.null, + ]), }) ), ]); @@ -224,7 +232,15 @@ export const CasesFindRequestRt = rt.exact( /** * Tags to filter by */ - tags: rt.union([limitedArraySchema(rt.string, 0, MAX_TAGS_FILTER_LENGTH, 'tags'), rt.string]), + tags: rt.union([ + limitedArraySchema({ + codec: rt.string, + fieldName: 'tags', + min: 0, + max: MAX_TAGS_FILTER_LENGTH, + }), + rt.string, + ]), /** * The status of the case (open, closed, in-progress) */ @@ -237,14 +253,24 @@ export const CasesFindRequestRt = rt.exact( * The uids of the user profiles to filter by */ assignees: rt.union([ - limitedArraySchema(rt.string, 0, MAX_ASSIGNEES_FILTER_LENGTH, 'assignees'), + limitedArraySchema({ + codec: rt.string, + fieldName: 'assignees', + min: 0, + max: MAX_ASSIGNEES_FILTER_LENGTH, + }), rt.string, ]), /** * The reporters to filter by */ reporters: rt.union([ - limitedArraySchema(rt.string, 0, MAX_REPORTERS_FILTER_LENGTH, 'reporters'), + limitedArraySchema({ + codec: rt.string, + fieldName: 'reporters', + min: 0, + max: MAX_REPORTERS_FILTER_LENGTH, + }), rt.string, ]), /** @@ -306,12 +332,12 @@ export const CasesFindRequestRt = rt.exact( }) ); -export const CasesDeleteRequestRt = limitedArraySchema( - NonEmptyString, - 1, - MAX_DELETE_IDS_LENGTH, - 'ids' -); +export const CasesDeleteRequestRt = limitedArraySchema({ + codec: NonEmptyString, + min: 1, + max: MAX_DELETE_IDS_LENGTH, + fieldName: 'ids', +}); export const CasesByAlertIDRequestRt = rt.exact( rt.partial({ @@ -370,7 +396,11 @@ export const CasePatchRequestRt = rt.intersection([ /** * The description of the case */ - description: limitedStringSchema('description', 1, MAX_DESCRIPTION_LENGTH), + description: limitedStringSchema({ + fieldName: 'description', + min: 1, + max: MAX_DESCRIPTION_LENGTH, + }), /** * The current status of the case (open, closed, in-progress) */ @@ -378,16 +408,16 @@ export const CasePatchRequestRt = rt.intersection([ /** * The identifying strings for filter a case */ - tags: limitedArraySchema( - limitedStringSchema('tag', 1, MAX_LENGTH_PER_TAG), - 0, - MAX_TAGS_PER_CASE, - 'tags' - ), + tags: limitedArraySchema({ + codec: limitedStringSchema({ fieldName: 'tag', min: 1, max: MAX_LENGTH_PER_TAG }), + min: 0, + max: MAX_TAGS_PER_CASE, + fieldName: 'tags', + }), /** * The title of a case */ - title: limitedStringSchema('title', 1, MAX_TITLE_LENGTH), + title: limitedStringSchema({ fieldName: 'title', min: 1, max: MAX_TITLE_LENGTH }), /** * The external system that the case can be synced with */ @@ -411,7 +441,10 @@ export const CasePatchRequestRt = rt.intersection([ /** * The category of the case. */ - category: rt.union([limitedStringSchema('category', 1, MAX_CATEGORY_LENGTH), rt.null]), + category: rt.union([ + limitedStringSchema({ fieldName: 'category', min: 1, max: MAX_CATEGORY_LENGTH }), + rt.null, + ]), }) ), /** @@ -477,7 +510,7 @@ export const GetCategoriesResponseRt = rt.array(rt.string); export const GetReportersResponseRt = rt.array(UserRt); export const CasesBulkGetRequestRt = rt.strict({ - ids: rt.array(rt.string), + ids: limitedArraySchema({ codec: rt.string, min: 1, max: MAX_BULK_GET_CASES, fieldName: 'ids' }), }); export const CasesBulkGetResponseRt = rt.strict({ diff --git a/x-pack/plugins/cases/common/api/cases/comment/files.ts b/x-pack/plugins/cases/common/api/cases/comment/files.ts index 04b46876d5188..38d70cdc45555 100644 --- a/x-pack/plugins/cases/common/api/cases/comment/files.ts +++ b/x-pack/plugins/cases/common/api/cases/comment/files.ts @@ -25,7 +25,12 @@ export type FileAttachmentMetadata = rt.TypeOf; const MIN_DELETE_IDS = 1; export const BulkDeleteFileAttachmentsRequestRt = rt.strict({ - ids: limitedArraySchema(NonEmptyString, MIN_DELETE_IDS, MAX_DELETE_FILES, 'ids'), + ids: limitedArraySchema({ + codec: NonEmptyString, + min: MIN_DELETE_IDS, + max: MAX_DELETE_FILES, + fieldName: 'ids', + }), }); export type BulkDeleteFileAttachmentsRequest = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/cases/comment/index.ts b/x-pack/plugins/cases/common/api/cases/comment/index.ts index f688dc24fdf85..1662631ae2b79 100644 --- a/x-pack/plugins/cases/common/api/cases/comment/index.ts +++ b/x-pack/plugins/cases/common/api/cases/comment/index.ts @@ -6,6 +6,8 @@ */ import * as rt from 'io-ts'; +import { MAX_BULK_GET_ATTACHMENTS } from '../../../constants'; +import { limitedArraySchema } from '../../../schema'; import { jsonValueRt } from '../../runtime_types'; import { NumberFromString } from '../../saved_object'; @@ -307,7 +309,12 @@ export const FindCommentsQueryParamsRt = rt.exact( export const BulkCreateCommentRequestRt = rt.array(CommentRequestRt); export const BulkGetAttachmentsRequestRt = rt.strict({ - ids: rt.array(rt.string), + ids: limitedArraySchema({ + codec: rt.string, + min: 1, + max: MAX_BULK_GET_ATTACHMENTS, + fieldName: 'ids', + }), }); export const BulkGetAttachmentsResponseRt = rt.strict({ diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index bee8ff9f3b17e..b7b4987042495 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -101,7 +101,7 @@ export const MAX_ALERTS_PER_CASE = 1000 as const; * Searching */ export const MAX_DOCS_PER_PAGE = 10000 as const; -export const MAX_BULK_GET_ATTACHMENTS = MAX_DOCS_PER_PAGE; +export const MAX_BULK_GET_ATTACHMENTS = 100 as const; export const MAX_CONCURRENT_SEARCHES = 10 as const; export const MAX_BULK_GET_CASES = 1000 as const; export const MAX_COMMENTS_PER_PAGE = 100 as const; diff --git a/x-pack/plugins/cases/common/schema/index.test.ts b/x-pack/plugins/cases/common/schema/index.test.ts index 826ee6ffbcd31..1a810c8003ff7 100644 --- a/x-pack/plugins/cases/common/schema/index.test.ts +++ b/x-pack/plugins/cases/common/schema/index.test.ts @@ -14,8 +14,11 @@ describe('schema', () => { const fieldName = 'foobar'; it('fails when given an empty string', () => { - expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1, fieldName).decode(['']))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report( + limitedArraySchema({ codec: NonEmptyString, fieldName, min: 1, max: 1 }).decode(['']) + ) + ).toMatchInlineSnapshot(` Array [ "string must have length >= 1", ] @@ -23,8 +26,11 @@ describe('schema', () => { }); it('fails when given an empty array', () => { - expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1, fieldName).decode([]))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report( + limitedArraySchema({ codec: NonEmptyString, fieldName, min: 1, max: 1 }).decode([]) + ) + ).toMatchInlineSnapshot(` Array [ "The length of the field foobar is too short. Array must be of length >= 1.", ] @@ -33,7 +39,12 @@ describe('schema', () => { it('fails when given an array larger than the limit of one item', () => { expect( - PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1, fieldName).decode(['a', 'b'])) + PathReporter.report( + limitedArraySchema({ codec: NonEmptyString, fieldName, min: 1, max: 1 }).decode([ + 'a', + 'b', + ]) + ) ).toMatchInlineSnapshot(` Array [ "The length of the field foobar is too long. Array must be of length <= 1.", @@ -42,8 +53,11 @@ describe('schema', () => { }); it('succeeds when given an array of 1 item with a non-empty string', () => { - expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1, fieldName).decode(['a']))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report( + limitedArraySchema({ codec: NonEmptyString, fieldName, min: 1, max: 1 }).decode(['a']) + ) + ).toMatchInlineSnapshot(` Array [ "No errors!", ] @@ -51,8 +65,11 @@ describe('schema', () => { }); it('succeeds when given an array of 0 item with a non-empty string when the min is 0', () => { - expect(PathReporter.report(limitedArraySchema(NonEmptyString, 0, 2, fieldName).decode([]))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report( + limitedArraySchema({ codec: NonEmptyString, fieldName, min: 0, max: 2 }).decode([]) + ) + ).toMatchInlineSnapshot(` Array [ "No errors!", ] @@ -64,7 +81,7 @@ describe('schema', () => { const fieldName = 'foo'; it('fails when given string is shorter than minimum', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 2, 1).decode('a'))) + expect(PathReporter.report(limitedStringSchema({ fieldName, min: 2, max: 1 }).decode('a'))) .toMatchInlineSnapshot(` Array [ "The length of the ${fieldName} is too short. The minimum length is 2.", @@ -73,7 +90,7 @@ describe('schema', () => { }); it('fails when given string is empty and minimum is not 0', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 1, 1).decode(''))) + expect(PathReporter.report(limitedStringSchema({ fieldName, min: 1, max: 1 }).decode(''))) .toMatchInlineSnapshot(` Array [ "The ${fieldName} field cannot be an empty string.", @@ -82,7 +99,7 @@ describe('schema', () => { }); it('fails when given string consists only empty characters and minimum is not 0', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 1, 1).decode(' '))) + expect(PathReporter.report(limitedStringSchema({ fieldName, min: 1, max: 1 }).decode(' '))) .toMatchInlineSnapshot(` Array [ "The ${fieldName} field cannot be an empty string.", @@ -91,8 +108,11 @@ describe('schema', () => { }); it('fails when given string is larger than maximum', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 1, 5).decode('Hello there!!'))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report( + limitedStringSchema({ fieldName, min: 1, max: 5 }).decode('Hello there!!') + ) + ).toMatchInlineSnapshot(` Array [ "The length of the ${fieldName} is too long. The maximum length is 5.", ] @@ -100,8 +120,9 @@ describe('schema', () => { }); it('succeeds when given string within limit', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 1, 50).decode('Hello!!'))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report(limitedStringSchema({ fieldName, min: 1, max: 50 }).decode('Hello!!')) + ).toMatchInlineSnapshot(` Array [ "No errors!", ] @@ -109,7 +130,7 @@ describe('schema', () => { }); it('succeeds when given string is empty and minimum is 0', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 0, 5).decode(''))) + expect(PathReporter.report(limitedStringSchema({ fieldName, min: 0, max: 5 }).decode(''))) .toMatchInlineSnapshot(` Array [ "No errors!", @@ -118,7 +139,7 @@ describe('schema', () => { }); it('succeeds when given string consists only empty characters and minimum is 0', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 0, 5).decode(' '))) + expect(PathReporter.report(limitedStringSchema({ fieldName, min: 0, max: 5 }).decode(' '))) .toMatchInlineSnapshot(` Array [ "No errors!", @@ -127,8 +148,9 @@ describe('schema', () => { }); it('succeeds when given string is same as maximum', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 0, 5).decode('Hello'))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report(limitedStringSchema({ fieldName, min: 0, max: 5 }).decode('Hello')) + ).toMatchInlineSnapshot(` Array [ "No errors!", ] @@ -136,8 +158,9 @@ describe('schema', () => { }); it('succeeds when given string is larger than maximum but same as maximum after trim', () => { - expect(PathReporter.report(limitedStringSchema(fieldName, 0, 5).decode('Hello '))) - .toMatchInlineSnapshot(` + expect( + PathReporter.report(limitedStringSchema({ fieldName, min: 0, max: 5 }).decode('Hello ')) + ).toMatchInlineSnapshot(` Array [ "No errors!", ] diff --git a/x-pack/plugins/cases/common/schema/index.ts b/x-pack/plugins/cases/common/schema/index.ts index 1f8e4e05c4933..9003360ae11b8 100644 --- a/x-pack/plugins/cases/common/schema/index.ts +++ b/x-pack/plugins/cases/common/schema/index.ts @@ -8,6 +8,12 @@ import * as rt from 'io-ts'; import { either } from 'fp-ts/lib/Either'; +export interface LimitedSchemaType { + fieldName: string; + min: number; + max: number; +} + export const NonEmptyString = new rt.Type( 'NonEmptyString', rt.string.is, @@ -22,7 +28,7 @@ export const NonEmptyString = new rt.Type( rt.identity ); -export const limitedStringSchema = (fieldName: string, min: number, max: number) => +export const limitedStringSchema = ({ fieldName, min, max }: LimitedSchemaType) => new rt.Type( 'LimitedString', rt.string.is, @@ -55,12 +61,12 @@ export const limitedStringSchema = (fieldName: string, min: number, max: number) rt.identity ); -export const limitedArraySchema = ( - codec: T, - min: number, - max: number, - fieldName: string -) => +export const limitedArraySchema = ({ + codec, + fieldName, + min, + max, +}: { codec: T } & LimitedSchemaType) => new rt.Type>, Array>, unknown>( 'LimitedArray', (input): input is T[] => rt.array(codec).is(input), diff --git a/x-pack/plugins/cases/kibana.jsonc b/x-pack/plugins/cases/kibana.jsonc index 6604dc63402ef..b0a03ad753e97 100644 --- a/x-pack/plugins/cases/kibana.jsonc +++ b/x-pack/plugins/cases/kibana.jsonc @@ -35,7 +35,8 @@ "home", "taskManager", "usageCollection", - "spaces" + "spaces", + "serverless", ], "requiredBundles": [], "extraPublicDirs": [ diff --git a/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts index 5062d73dd9b2f..3aa4c02457ef7 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts @@ -13,7 +13,7 @@ import { } from '../kibana_react.mock'; export const KibanaServices = { - get: jest.fn(), + get: jest.fn(() => ({})), getKibanaVersion: jest.fn(() => '8.0.0'), getConfig: jest.fn(() => null), }; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/services.ts b/x-pack/plugins/cases/public/common/lib/kibana/services.ts index b1248488e5286..1846548c5b2b4 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/services.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/services.ts @@ -7,8 +7,10 @@ import type { CoreStart } from '@kbn/core/public'; import type { CasesUiConfigType } from '../../../../common/ui/types'; +import type { CasesPluginStart } from '../../../types'; -type GlobalServices = Pick; +type GlobalServices = Pick & + Pick; export class KibanaServices { private static kibanaVersion?: string; @@ -19,13 +21,14 @@ export class KibanaServices { application, config, http, + serverless, kibanaVersion, theme, }: GlobalServices & { kibanaVersion: string; config: CasesUiConfigType; }) { - this.services = { application, http, theme }; + this.services = { application, http, theme, serverless }; this.kibanaVersion = kibanaVersion; this.config = config; } diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index 7ce32a2f123a5..7661242fe9153 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -20,6 +20,7 @@ import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; +jest.mock('../../common/lib/kibana'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/use_get_action_license', () => { return { 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 bd604514b4cfa..1ef1d870daf77 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 @@ -566,7 +566,8 @@ describe('Case View Page activity tab', () => { }); }); - describe('User actions', () => { + // FLAKY: https://github.com/elastic/kibana/issues/151981 + describe.skip('User actions', () => { it('renders the description correctly', async () => { appMockRender = createAppMockRenderer(); appMockRender.render(); diff --git a/x-pack/plugins/cases/public/components/create/index.test.tsx b/x-pack/plugins/cases/public/components/create/index.test.tsx index 29f89226c61af..cb4b135212e0c 100644 --- a/x-pack/plugins/cases/public/components/create/index.test.tsx +++ b/x-pack/plugins/cases/public/components/create/index.test.tsx @@ -33,6 +33,7 @@ import { CreateCase } from '.'; import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; +jest.mock('../../common/lib/kibana'); jest.mock('../../containers/api'); jest.mock('../../containers/user_profiles/api'); jest.mock('../../containers/use_get_tags'); diff --git a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx index 7a52686e64378..bdb726701adf6 100644 --- a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx @@ -14,11 +14,19 @@ import { CasesDeepLinkId } from '../../common/navigation'; const mockSetBreadcrumbs = jest.fn(); const mockSetTitle = jest.fn(); +const mockSetServerlessBreadcrumbs = jest.fn(); +const mockGetKibanaServices = jest.fn((): unknown => ({ + serverless: { setBreadcrumbs: mockSetServerlessBreadcrumbs }, +})); jest.mock('../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../common/lib/kibana'); return { ...originalModule, + KibanaServices: { + ...originalModule.KibanaServices, + get: () => mockGetKibanaServices(), + }, useNavigation: jest.fn().mockReturnValue({ getAppUrl: jest.fn((params?: { deepLinkId: string }) => params?.deepLinkId ?? '/test'), }), @@ -50,12 +58,19 @@ describe('useCasesBreadcrumbs', () => { { href: '/test', onClick: expect.any(Function), text: 'Test' }, { text: 'Cases' }, ]); + expect(mockSetServerlessBreadcrumbs).toHaveBeenCalledWith([]); }); it('should sets the cases title', () => { renderHook(() => useCasesBreadcrumbs(CasesDeepLinkId.cases), { wrapper }); expect(mockSetTitle).toHaveBeenCalledWith(['Cases', 'Test']); }); + + it('should not set serverless breadcrumbs in ess', () => { + mockGetKibanaServices.mockReturnValueOnce({ serverless: undefined }); + renderHook(() => useCasesBreadcrumbs(CasesDeepLinkId.cases), { wrapper }); + expect(mockSetServerlessBreadcrumbs).not.toHaveBeenCalled(); + }); }); describe('set create_case breadcrumbs', () => { @@ -66,12 +81,19 @@ describe('useCasesBreadcrumbs', () => { { href: CasesDeepLinkId.cases, onClick: expect.any(Function), text: 'Cases' }, { text: 'Create' }, ]); + expect(mockSetServerlessBreadcrumbs).toHaveBeenCalledWith([]); }); it('should sets the cases title', () => { renderHook(() => useCasesBreadcrumbs(CasesDeepLinkId.casesCreate), { wrapper }); expect(mockSetTitle).toHaveBeenCalledWith(['Create', 'Cases', 'Test']); }); + + it('should not set serverless breadcrumbs in ess', () => { + mockGetKibanaServices.mockReturnValueOnce({ serverless: undefined }); + renderHook(() => useCasesBreadcrumbs(CasesDeepLinkId.casesCreate), { wrapper }); + expect(mockSetServerlessBreadcrumbs).not.toHaveBeenCalled(); + }); }); describe('set case_view breadcrumbs', () => { @@ -83,11 +105,18 @@ describe('useCasesBreadcrumbs', () => { { href: CasesDeepLinkId.cases, onClick: expect.any(Function), text: 'Cases' }, { text: title }, ]); + expect(mockSetServerlessBreadcrumbs).toHaveBeenCalledWith([{ text: title }]); }); it('should sets the cases title', () => { renderHook(() => useCasesTitleBreadcrumbs(title), { wrapper }); expect(mockSetTitle).toHaveBeenCalledWith([title, 'Cases', 'Test']); }); + + it('should not set serverless breadcrumbs in ess', () => { + mockGetKibanaServices.mockReturnValueOnce({ serverless: undefined }); + renderHook(() => useCasesTitleBreadcrumbs(title), { wrapper }); + expect(mockSetServerlessBreadcrumbs).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.ts b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.ts index 68a37f8252f05..24d1fa79e0d14 100644 --- a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.ts +++ b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { ChromeBreadcrumb } from '@kbn/core/public'; import { useCallback, useEffect } from 'react'; -import { useKibana, useNavigation } from '../../common/lib/kibana'; +import { KibanaServices, useKibana, useNavigation } from '../../common/lib/kibana'; import type { ICasesDeepLinkId } from '../../common/navigation'; import { CasesDeepLinkId } from '../../common/navigation'; import { useCasesContext } from '../cases_context/use_cases_context'; @@ -84,6 +84,7 @@ export const useCasesBreadcrumbs = (pageDeepLink: ICasesDeepLinkId) => { ] : []), ]); + KibanaServices.get().serverless?.setBreadcrumbs([]); }, [pageDeepLink, appTitle, getAppUrl, applyBreadcrumbs]); }; @@ -93,16 +94,18 @@ export const useCasesTitleBreadcrumbs = (caseTitle: string) => { const applyBreadcrumbs = useApplyBreadcrumbs(); useEffect(() => { + const titleBreadcrumb: ChromeBreadcrumb = { + text: caseTitle, + }; const casesBreadcrumbs: ChromeBreadcrumb[] = [ { text: appTitle, href: getAppUrl() }, { text: casesBreadcrumbTitle[CasesDeepLinkId.cases], href: getAppUrl({ deepLinkId: CasesDeepLinkId.cases }), }, - { - text: caseTitle, - }, + titleBreadcrumb, ]; applyBreadcrumbs(casesBreadcrumbs); + KibanaServices.get().serverless?.setBreadcrumbs([titleBreadcrumb]); }, [caseTitle, appTitle, getAppUrl, applyBreadcrumbs]); }; diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 6206a3304c88d..c5a1fd8c73a69 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -25,6 +25,7 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { FilesSetup, FilesStart } from '@kbn/files-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; import type { CasesBulkGetRequest, @@ -58,6 +59,7 @@ import type { PersistableStateAttachmentTypeRegistry } from './client/attachment export interface CasesPluginSetup { files: FilesSetup; security: SecurityPluginSetup; + serverless?: ServerlessPluginSetup; management: ManagementSetup; home?: HomePublicPluginSetup; } @@ -72,6 +74,7 @@ export interface CasesPluginStart { licensing?: LicensingPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; security: SecurityPluginStart; + serverless?: ServerlessPluginStart; spaces?: SpacesPluginStart; storage: Storage; triggersActionsUi: TriggersActionsStart; diff --git a/x-pack/plugins/cases/server/client/attachments/bulk_get.test.ts b/x-pack/plugins/cases/server/client/attachments/bulk_get.test.ts new file mode 100644 index 0000000000000..078f79128391b --- /dev/null +++ b/x-pack/plugins/cases/server/client/attachments/bulk_get.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MAX_BULK_GET_ATTACHMENTS } from '../../../common/constants'; +import { createCasesClientMockArgs, createCasesClientMock } from '../mocks'; +import { bulkGet } from './bulk_get'; + +describe('bulkGet', () => { + describe('errors', () => { + const casesClient = createCasesClientMock(); + const clientArgs = createCasesClientMockArgs(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it(`throws when trying to fetch more than ${MAX_BULK_GET_ATTACHMENTS} attachments`, async () => { + await expect( + bulkGet( + { attachmentIDs: Array(MAX_BULK_GET_ATTACHMENTS + 1).fill('foobar'), caseID: '123' }, + clientArgs, + casesClient + ) + ).rejects.toThrow( + `Error: The length of the field ids is too long. Array must be of length <= ${MAX_BULK_GET_ATTACHMENTS}.` + ); + }); + + it('throws when trying to fetch zero attachments', async () => { + await expect( + bulkGet({ attachmentIDs: [], caseID: '123' }, clientArgs, casesClient) + ).rejects.toThrow( + 'Error: The length of the field ids is too short. Array must be of length >= 1.' + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/server/client/attachments/bulk_get.ts b/x-pack/plugins/cases/server/client/attachments/bulk_get.ts index 294b39bab6959..553f8ca258669 100644 --- a/x-pack/plugins/cases/server/client/attachments/bulk_get.ts +++ b/x-pack/plugins/cases/server/client/attachments/bulk_get.ts @@ -5,10 +5,7 @@ * 2.0. */ -import Boom from '@hapi/boom'; - import { partition } from 'lodash'; -import { MAX_BULK_GET_ATTACHMENTS } from '../../../common/constants'; import type { BulkGetAttachmentsResponse, CommentAttributes } from '../../../common/api'; import { decodeWithExcessOrThrow, @@ -45,8 +42,6 @@ export async function bulkGet( try { const request = decodeWithExcessOrThrow(BulkGetAttachmentsRequestRt)({ ids: attachmentIDs }); - throwErrorIfIdsExceedTheLimit(request.ids); - // perform an authorization check for the case await casesClient.cases.resolve({ id: caseID }); @@ -83,14 +78,6 @@ export async function bulkGet( } } -const throwErrorIfIdsExceedTheLimit = (ids: string[]) => { - if (ids.length > MAX_BULK_GET_ATTACHMENTS) { - throw Boom.badRequest( - `Maximum request limit of ${MAX_BULK_GET_ATTACHMENTS} attachments reached` - ); - } -}; - interface PartitionedAttachments { validAttachments: AttachmentSavedObject[]; attachmentsWithErrors: AttachmentSavedObjectWithErrors; diff --git a/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts index 0ee8955c9adb5..771f5ece6f188 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts @@ -5,22 +5,29 @@ * 2.0. */ +import { MAX_BULK_GET_CASES } from '../../../common/constants'; import { createCasesClientMockArgs } from '../mocks'; import { bulkGet } from './bulk_get'; describe('bulkGet', () => { - describe('throwErrorIfCaseIdsReachTheLimit', () => { + describe('errors', () => { const clientArgs = createCasesClientMockArgs(); beforeEach(() => { jest.clearAllMocks(); }); - it('throws if the requested cases are more than 1000', async () => { - const ids = Array(1001).fill('test'); + it(`throws when trying to fetch more than ${MAX_BULK_GET_CASES} cases`, async () => { + await expect( + bulkGet({ ids: Array(MAX_BULK_GET_CASES + 1).fill('foobar') }, clientArgs) + ).rejects.toThrow( + `Error: The length of the field ids is too long. Array must be of length <= ${MAX_BULK_GET_CASES}.` + ); + }); - await expect(bulkGet({ ids }, clientArgs)).rejects.toThrow( - 'Maximum request limit of 1000 cases reached' + it('throws when trying to fetch zero cases', async () => { + await expect(bulkGet({ ids: [] }, clientArgs)).rejects.toThrow( + 'Error: The length of the field ids is too short. Array must be of length >= 1.' ); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_get.ts b/x-pack/plugins/cases/server/client/cases/bulk_get.ts index 13df81bf06965..4665c27cebbf6 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_get.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_get.ts @@ -5,10 +5,8 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { partition } from 'lodash'; -import { MAX_BULK_GET_CASES } from '../../../common/constants'; import type { CasesBulkGetResponse, CasesBulkGetRequest, @@ -45,8 +43,6 @@ export const bulkGet = async ( try { const request = decodeWithExcessOrThrow(CasesBulkGetRequestRt)(params); - throwErrorIfCaseIdsReachTheLimit(request.ids); - const cases = await caseService.getCases({ caseIds: request.ids }); const [validCases, soBulkGetErrors] = partition( @@ -91,12 +87,6 @@ export const bulkGet = async ( } }; -const throwErrorIfCaseIdsReachTheLimit = (ids: string[]) => { - if (ids.length > MAX_BULK_GET_CASES) { - throw Boom.badRequest(`Maximum request limit of ${MAX_BULK_GET_CASES} cases reached`); - } -}; - const constructErrors = ( soBulkGetErrors: CaseSavedObjectWithErrors, unauthorizedCases: CaseSavedObjectTransformed[] diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 4314e82ce6ba7..26e61c5c923aa 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -67,6 +67,7 @@ "@kbn/core-lifecycle-browser", "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-theme-browser", + "@kbn/serverless", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 8b6ed65bb1853..cc01567f55347 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -8,6 +8,8 @@ import { PostureTypes, VulnSeverity } from './types'; export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status'; +export const STATUS_API_CURRENT_VERSION = '1'; + export const STATS_ROUTE_PATH = '/internal/cloud_security_posture/stats/{policy_template}'; export const VULNERABILITIES_DASHBOARD_ROUTE_PATH = diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_setup_status_api.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_setup_status_api.ts index 99e277b30bb54..afb7a89c69e6f 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_setup_status_api.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_setup_status_api.ts @@ -8,7 +8,7 @@ import { useQuery, type UseQueryOptions } from '@tanstack/react-query'; import { useKibana } from '../hooks/use_kibana'; import { type CspSetupStatus } from '../../../common/types'; -import { STATUS_ROUTE_PATH } from '../../../common/constants'; +import { STATUS_API_CURRENT_VERSION, STATUS_ROUTE_PATH } from '../../../common/constants'; const getCspSetupStatusQueryKey = 'csp_status_key'; @@ -18,7 +18,7 @@ export const useCspSetupStatusApi = ( const { http } = useKibana().services; return useQuery( [getCspSetupStatusQueryKey], - () => http.get(STATUS_ROUTE_PATH), + () => http.get(STATUS_ROUTE_PATH, { version: STATUS_API_CURRENT_VERSION }), options ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_rules.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_rules.ts index a5aee47c7c1cc..e0870d1e64b01 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_rules.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_rules.ts @@ -27,7 +27,7 @@ export const useFindCspRuleTemplates = ( [CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE, { search, page, perPage, packagePolicyId }], () => { return http.get(FIND_CSP_RULE_TEMPLATE_ROUTE_PATH, { - query: { packagePolicyId, page, perPage }, + query: { packagePolicyId, page, perPage, search }, version: FIND_CSP_RULE_TEMPLATE_API_CURRENT_VERSION, }); } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/csp_rule_template/get_csp_rule_template.ts b/x-pack/plugins/cloud_security_posture/server/routes/csp_rule_template/get_csp_rule_template.ts index 6d41f87eaff7f..fb6e0e7c53ab0 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/csp_rule_template/get_csp_rule_template.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/csp_rule_template/get_csp_rule_template.ts @@ -7,7 +7,6 @@ import { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import pMap from 'p-map'; import { transformError } from '@kbn/securitysolution-es-utils'; import { GetCspRuleTemplateRequest, GetCspRuleTemplateResponse } from '../../../common/types'; import { CspRuleTemplate } from '../../../common/schemas'; @@ -61,12 +60,8 @@ const findCspRuleTemplateHandler = async ( filter: getBenchmarkTypeFilter(benchmarkId), }); - const cspRulesTemplates = await pMap( - cspRulesTemplatesSo.saved_objects, - async (cspRuleTemplate) => { - return { ...cspRuleTemplate.attributes }; - }, - { concurrency: 50 } + const cspRulesTemplates = cspRulesTemplatesSo.saved_objects.map( + (cspRuleTemplate) => cspRuleTemplate.attributes ); return { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts index 60f634b4c32b8..86b0d0a66802b 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts @@ -16,6 +16,7 @@ import type { import moment from 'moment'; import { Installation, PackagePolicy } from '@kbn/fleet-plugin/common'; import { schema } from '@kbn/config-schema'; +import { VersionedRoute } from '@kbn/core-http-server/src/versioning/types'; import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME, STATUS_ROUTE_PATH, @@ -29,7 +30,12 @@ import { LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, VULN_MGMT_POLICY_TEMPLATE, } from '../../../common/constants'; -import type { CspApiRequestHandlerContext, CspRouter, StatusResponseInfo } from '../../types'; +import type { + CspApiRequestHandlerContext, + CspRequestHandlerContext, + CspRouter, + StatusResponseInfo, +} from '../../types'; import type { CspSetupStatus, CspStatusCode, @@ -328,44 +334,55 @@ export const statusQueryParamsSchema = schema.object({ check: schema.oneOf([schema.literal('all'), schema.literal('init')], { defaultValue: 'all' }), }); -export const defineGetCspStatusRoute = (router: CspRouter): void => - router.get( - { +export const defineGetCspStatusRoute = ( + router: CspRouter +): VersionedRoute<'get', CspRequestHandlerContext> => + router.versioned + .get({ + access: 'internal', path: STATUS_ROUTE_PATH, - validate: { query: statusQueryParamsSchema }, options: { tags: ['access:cloud-security-posture-read'], }, - }, - async (context, request, response) => { - const cspContext = await context.csp; - try { - if (request.query.check === 'init') { + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: statusQueryParamsSchema, + }, + }, + }, + async (context, request, response) => { + const cspContext = await context.csp; + try { + if (request.query.check === 'init') { + return response.ok({ + body: { + isPluginInitialized: cspContext.isPluginInitialized(), + }, + }); + } + const status: CspSetupStatus = await getCspStatus({ + ...cspContext, + esClient: cspContext.esClient.asCurrentUser, + }); return response.ok({ - body: { - isPluginInitialized: cspContext.isPluginInitialized(), - }, + body: status, + }); + } catch (err) { + cspContext.logger.error(`Error getting csp status`); + cspContext.logger.error(err); + + const error = transformError(err); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, }); } - const status = await getCspStatus({ - ...cspContext, - esClient: cspContext.esClient.asCurrentUser, - }); - return response.ok({ - body: status, - }); - } catch (err) { - cspContext.logger.error(`Error getting csp status`); - cspContext.logger.error(err); - - const error = transformError(err); - return response.customError({ - body: { message: error.message }, - statusCode: error.statusCode, - }); } - } - ); + ); const getStatusResponse = (statusResponseInfo: StatusResponseInfo) => { const { diff --git a/x-pack/plugins/cloud_security_posture/tsconfig.json b/x-pack/plugins/cloud_security_posture/tsconfig.json index 6dd9ba33a3165..ae8f7d610002b 100755 --- a/x-pack/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/plugins/cloud_security_posture/tsconfig.json @@ -48,6 +48,7 @@ "@kbn/shared-ux-router", "@kbn/core-saved-objects-server", "@kbn/share-plugin", + "@kbn/core-http-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts b/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts index 2fd4e88348598..38a6024de277c 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts @@ -5,19 +5,26 @@ * 2.0. */ +import { IconType } from '@elastic/eui'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; +import { IndexPattern } from '@kbn/io-ts-utils'; import { DatasetId, DatasetType, IntegrationType } from '../types'; type IntegrationBase = Pick; +interface DatasetDeps extends DatasetType { + iconType?: IconType; +} export class Dataset { id: DatasetId; + iconType?: IconType; name: DatasetType['name']; - title: DatasetType['title']; + title: string; parentIntegration?: IntegrationBase; - private constructor(dataset: DatasetType, parentIntegration?: IntegrationType) { + private constructor(dataset: DatasetDeps, parentIntegration?: IntegrationBase) { this.id = `dataset-${dataset.name}` as DatasetId; + this.iconType = dataset.iconType; this.name = dataset.name; this.title = dataset.title ?? dataset.name; this.parentIntegration = parentIntegration && { @@ -26,16 +33,37 @@ export class Dataset { }; } + getFullTitle(): string { + return this.parentIntegration?.name + ? `[${this.parentIntegration.name}] ${this.title}` + : this.title; + } + toDataviewSpec(): DataViewSpec { // Invert the property because the API returns the index pattern as `name` and a readable name as `title` return { id: this.id, - name: this.title, - title: this.name, + name: this.getFullTitle(), + title: this.name as string, }; } - public static create(dataset: DatasetType, parentIntegration?: IntegrationType) { + toPlain() { + return { + name: this.name, + title: this.title, + }; + } + + public static create(dataset: DatasetDeps, parentIntegration?: IntegrationBase) { return new Dataset(dataset, parentIntegration); } + + public static createAllLogsDataset() { + return new Dataset({ + name: 'logs-*-*' as IndexPattern, + title: 'All log datasets', + iconType: 'editorChecklist', + }); + } } diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx index ddd47351b9917..d96cdc07bd9bc 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx @@ -13,11 +13,12 @@ import type { Meta, Story } from '@storybook/react'; import { IndexPattern } from '@kbn/io-ts-utils'; import { Dataset, Integration } from '../../../common/datasets'; import { DatasetSelector } from './dataset_selector'; +import { DatasetSelectorProps, DatasetsSelectorSearchParams } from './types'; import { - DatasetSelectionHandler, - DatasetSelectorProps, - DatasetsSelectorSearchParams, -} from './types'; + AllDatasetSelection, + DatasetSelection, + DatasetSelectionChange, +} from '../../utils/dataset_selection'; const meta: Meta = { component: DatasetSelector, @@ -37,7 +38,9 @@ const meta: Meta = { export default meta; const DatasetSelectorTemplate: Story = (args) => { - const [selected, setSelected] = useState(() => mockIntegrations[0].datasets[0]); + const [datasetSelection, setDatasetSelection] = useState(() => + AllDatasetSelection.create() + ); const [search, setSearch] = useState({ sortOrder: 'asc', @@ -51,8 +54,8 @@ const DatasetSelectorTemplate: Story = (args) => { } }; - const onDatasetSelected: DatasetSelectionHandler = (dataset) => { - setSelected(dataset); + const onSelectionChange: DatasetSelectionChange = (newSelection) => { + setDatasetSelection(newSelection); }; const filteredIntegrations = integrations.filter((integration) => @@ -72,14 +75,14 @@ const DatasetSelectorTemplate: Story = (args) => { diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx index 19718e580d049..aa1fb057bbd32 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -22,7 +22,12 @@ import { DatasetsPopover } from './sub_components/datasets_popover'; import { DatasetSkeleton } from './sub_components/datasets_skeleton'; import { SearchControls } from './sub_components/search_controls'; import { DatasetSelectorProps } from './types'; -import { buildIntegrationsTree } from './utils'; +import { + buildIntegrationsTree, + createAllLogDatasetsItem, + createUnmanagedDatasetsItem, + createIntegrationStatusItem, +} from './utils'; /** * Lazy load hidden components @@ -30,12 +35,11 @@ import { buildIntegrationsTree } from './utils'; const DatasetsList = dynamic(() => import('./sub_components/datasets_list'), { fallback: , }); -const IntegrationsListStatus = dynamic(() => import('./sub_components/integrations_list_status')); export function DatasetSelector({ datasets, datasetsError, - initialSelected, + datasetSelection, integrations, integrationsError, isLoadingIntegrations, @@ -46,7 +50,7 @@ export function DatasetSelector({ onIntegrationsSort, onIntegrationsStreamsSearch, onIntegrationsStreamsSort, - onDatasetSelected, + onSelectionChange, onStreamsEntryClick, onUnmanagedStreamsReload, onUnmanagedStreamsSearch, @@ -56,16 +60,16 @@ export function DatasetSelector({ isOpen, panelId, search, - selected, closePopover, changePanel, scrollToIntegrationsBottom, searchByName, + selectAllLogDataset, selectDataset, sortByOrder, togglePopover, } = useDatasetSelector({ - initialContext: { selected: initialSelected }, + initialContext: { selection: datasetSelection }, onIntegrationsLoadMore, onIntegrationsReload, onIntegrationsSearch, @@ -75,32 +79,26 @@ export function DatasetSelector({ onUnmanagedStreamsSearch, onUnmanagedStreamsSort, onUnmanagedStreamsReload, - onDatasetSelected, + onSelectionChange, }); const [setSpyRef] = useIntersectionRef({ onIntersecting: scrollToIntegrationsBottom }); const { items: integrationItems, panels: integrationPanels } = useMemo(() => { - const datasetsItem = { - name: uncategorizedLabel, - onClick: onStreamsEntryClick, - panel: UNMANAGED_STREAMS_PANEL_ID, - }; - - const createIntegrationStatusItem = () => ({ - disabled: true, - name: ( - - ), - }); + const allLogDatasetsItem = createAllLogDatasetsItem({ onClick: selectAllLogDataset }); + const unmanagedDatasetsItem = createUnmanagedDatasetsItem({ onClick: onStreamsEntryClick }); if (!integrations || integrations.length === 0) { return { - items: [datasetsItem, createIntegrationStatusItem()], + items: [ + allLogDatasetsItem, + unmanagedDatasetsItem, + createIntegrationStatusItem({ + error: integrationsError, + integrations, + onRetry: onIntegrationsReload, + }), + ], panels: [], }; } @@ -112,12 +110,13 @@ export function DatasetSelector({ }); return { - items: [datasetsItem, ...items], + items: [allLogDatasetsItem, unmanagedDatasetsItem, ...items], panels, }; }, [ integrations, integrationsError, + selectAllLogDataset, selectDataset, onIntegrationsReload, onStreamsEntryClick, @@ -150,7 +149,7 @@ export function DatasetSelector({ return ( ); diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/defaults.ts b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/defaults.ts index a0fce7ed56730..a818c6645bda7 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/defaults.ts +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/defaults.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AllDatasetSelection } from '../../../utils/dataset_selection'; import { HashedCache } from '../../../../common/hashed_cache'; import { INTEGRATION_PANEL_ID } from '../constants'; import { DatasetsSelectorSearchParams } from '../types'; @@ -16,6 +17,7 @@ export const defaultSearch: DatasetsSelectorSearchParams = { }; export const DEFAULT_CONTEXT: DefaultDatasetsSelectorContext = { + selection: AllDatasetSelection.create(), searchCache: new HashedCache(), panelId: INTEGRATION_PANEL_ID, search: defaultSearch, diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/state_machine.ts b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/state_machine.ts index a315e4925726d..aea09e7c6f2e7 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/state_machine.ts @@ -6,6 +6,7 @@ */ import { actions, assign, createMachine, raise } from 'xstate'; +import { AllDatasetSelection, SingleDatasetSelection } from '../../../utils/dataset_selection'; import { UNMANAGED_STREAMS_PANEL_ID } from '../constants'; import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { @@ -19,83 +20,107 @@ import { export const createPureDatasetsSelectorStateMachine = ( initialContext: Partial = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6Ew87CAYgBUB5AcQ4BkBRAbQAMAXUSgADgwCW6KeQB2YkAA9EAVgBMAGhABPRAA4AjNTWDzggJxqjAFgODbANicBfVzrSZsuAsTJU1OTiYPJMAMLcbHgCIkqSsDJyikgqiEZGLtTO9hrWBgZqThpqOvoItraC1EYA7E6CdgYAzIJFghrN7p4YWDj4RKQUNMGhrJw8saKpCUkKSqoIGQ3ULrWWjU6WzbW2dmXqzbbZarUaxmZqzoJuHiBefb6DASMh8tSEUrCy8lAAkvJ0GAoJQMMlYBEABIAQQAchxeAB9AAKcN43CE0wk0lk81Si1szRMLTU2xsTk0RksBgOCEstQM1B2Ng6jlsGnOtW6916PgG-mGQTeHy+P3+gOBoNx8gh4Rh8KRqNh6P4RixIFm0oWiEJxOapP1mUp1NpRUs1BuGlqzX1DnaGm5Dz5fiGgVG70+3ykvwBQJBYIUELw4QASmxuNxEexEX9YSxeBwQ9CWH82LC8IiAEJsFjsACymPiOOS2oQBlqFosVerzVp9Q0NVqVJtnWpxiMjt5-RdLyFoRFXp9Ev90qDvGhIblWYAmojYdC81Mi4ktfj0ldVpY7OZ2fTbKcaXpEE5moz6mpSQyjESbAZO95u89Be6B2LfZKAzKmHg2CGWDPEV-ZBeBDQsZmLPFQEWTIGxaDRBCJKwMlOIxTSMNQmVJDRilPJx2Ww+9Hn5V1Xn7T032HKVkjwdBKDAVAAFtZXlBEUTRDE4nAlcSzXBBigwqk1COWp1gMSx6UPcpb1MYomy2NoXEI50nzdYVyO9cU-SohQaLoxixz4cJ-2QZNoRiFgwOxbjILSJZr0ZJx1kcm1TyKEpaQpBtLXqLZWmaJwOzuJ1HwFVSyNFDT3xHajaPopjv3HSdIQA+dF0sjUIJSKDDCqahsKKIwEJbIw8lpZpLGOdCT1k7YGXLLogq7J5QtIj0IqHLTP10uKg1-f9M1nICQPSzUeOyst7ErcrCo6ToOUsWkrWqWozA6a1HLaCqGp6B9mpIvs2sHKAAFV5AY1B5FQGAIG6-SoThVilRVTirLmLLbP4mprGE0TxIZWlLCtahyzMQp-O2c5bh2oie2fNT2pOs6LquyBbvimJDOM0zzJGzLSwyU9Vic60bUKfiAcqbINCpdkXBaOxbCUkL9pfdTflO87LuutGxwnKcBrnBcly4t7SwMXLGgK6m5v82paQcZpsicMTsLsalKi5RrduI3tWYRjnke52K7p-P8AKG0CXoy6z3sWcWKwMeCrWw2oOjpjyHAtWSyfQsw8ncO55HICA4CUYK9peZdRd4gBaRlxITxPE7OWkY4wpPHEKQSjiZiPBToBhICj1dxuvQkvqEuoxNw6wPMcGSrT9i9rihnltdhsL3tGmzFnKytqwH2sjyWFo8tWq1Zc2wlc51uH+wAC1FYuxtswqnAb+w6lPHYjEd2l92OfVtgCtp1gvQLoeUlqDtfSLKM-eARZL1ebg34xrRaJtxflk9VhKRwSpbAZCJGeHdWq3w6h+aUPNl490MIICslhlZnAaMsfUpoOQNy2I7eCt4L5txhipcBbNEacxRjdY2TFYG23gYg5B2FGiZHQcPbYxIVo3DpnsAwFIA6uCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSAAeiALQBmAKzUALAEZDAdgBslk4ckAmEwA5HhkwBoQAT0T6LakdHFzMLMLN9dzsAXxjvNExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kbUQzSQBOY0szd31zE0lrJutrbz8EVxdTM0solya3YP64hIwsHHwiUgoaeiYWajyC-iExKVk6qpV1TR0ERr7qa0cwx0lJE0tLF0l9QcQTbtveyXa1gB+j6LnmIESSxSq3SGyyOR2tHy1EIilgKjUUAAkmp0GAoJQMDVYEUABIAQQAcgJRAB9AAKVNEwiOlQ41XOdUu+iit0MhjMtgsnQFjgGvkQTQBQV6YXMhncj0c4MhyRWaXWmS2uSRahRaIx2Nx+MJZzUJMKFOpdMZlOZ4jMx3k7LNF0QhiBpn6Hh+7kMTU+EoQ70s1EkHmBjiM+ksTWV8QhizVqTWGU22W2u316MUmJxeIJRPUJLwhQASnxhMJafxaVjKTxRAIy+SeFi+JS8LSAEJ8Hj8ACyrJOLpqboQujMJn01Bad2e7m6Fi8QcsYuoCpMTXGTl9thVSeWKdhWozOuRqJzeeNhbNJdE5LLlp7AE1aZTyQPyk6QKcx1y9DMJomlnBxHDjacWhcCZLC+YN3GoSwekkN4106awpgPJIjxhTV0wRLNL0NfMTSLc0uDwPgyx4V9aSo5BRDLYdnSUV0AInMJjHuCYxSaJDgiQww4MaZ5Z3MZxXHcFwFUsLCoXVVM4W1RELwNXMjQLU1iQo5lREKGjySrWkSgELtkFbckyh4Zjf1HTlQEuRobDDGwjG3cwzCmcUhhcEEgj6R4XGsF5DDk5NcLTeFM11bNiJvLT1DwdBKDAVAAFsLStGkGSZFkKhHVj-wc-wFWoRoRRE-QAUMFw4N6UNrE8qwWgmHkarCnCNUi5TCLU69NLIpKUvS+9H2fbs3w-L8bL-ez6gQBwTFMKMozXUIPSqoSgy3JbBOA-R-iicZQoTVVOsU08CJioj1JI28aiG1KMooqiaImuiywYpj8pYjlamKhBYyW7pmmeMIWhMZw4J2xDkMkKYtzCeGOuhLqlLPFS9Ru-rSLNR6Rp0sR9NpcyeEs0RrJ+2zCrmxz4eMH4mheYCXmeWqgwwyRqCCtdGtsD0ejBU7D1Ri78Oi1SrygABVNQ0tQNRUBgCB8eey0qWy217Sp2b-vmzpHGoEE7n5qMQ1goNGnMI2mYO6YpJklGFJPcXzyxvqZblhWlcgVXRqfUlaKm782RpvXLhko2nPpxwkMFQMhk44wHHCaN-T4kwnePPCord2L1Nl+XFeVv2Xuo2j6MYma7PDxA3FGPjt1trcmYmYSLGT6rIkMNyM6ziL0auyXDUL72S+Sp77yJmjSfJymf118cnOsMNIZMDC1wcBUtsT54QKlF4tysRH1-7tHqGwDUagv9TiEJvSDKMkyzIsqzq7D8cMKCbpIOcRqgpQnBAUG43BAm6Hbe4LghYLGwqLE8l81jXyUJiO+ZRp4k1fhTd+f1xy6D-rDDwrVRS2G8noA6MpBRAjXNuAB8YYHyWzhkBBZBr6oEIIQe+xNZ5vx1jXcc25uYBACL5VwUppgJz0K8bm1gapRgBFOeucQExqHIBAOAmgzpwPWKHHB7EDDTlAk4CCIIap2Dgng0qQVZEfGnAEV40DEywOdjnbUOi2IAzwS0Qx4FnAmOgiuIYAlEIhGXLxHunQz5i1ztQNgHBIBuKKvNSIRhubTFavYSIHw4IxlDN0f+bwarhkaNYSJLtom7ASbTPQzhQwtCMb4qCMFoY9GkeBKc0xXABBKcLJxjDB4Sz1AACwNJU2uE4lpPHlL5HofRmqOGhkzI2dxtwHQcHYbcpSXEY16lLO6CVzSjKXlzIw9gpRVTuCCdmPlgE2G3FOCwionCbO6ts66Hs9mDQniNQ57F-SG3kSEIK3EXD2DqlORCnk2gRBsSEZ5-S87Y09kXH2KsvkZR+QDGqS0oFM39HYFOUxhJPC5j-dZx93LdPoeFc+zD3HU10R48Cu1wwvDaoCGwATAIyKWbIwUrwQawp6QwgeF9cJINvmADF81dC2EQrGEFTgeRFLaHBN4phtz+k2h6DCvk4WiqvuoagbDCBSu5NymMwV3BOCsDGTlwYubAQsAGcM2qoH6CUTEIAA */ createMachine( { context: { ...DEFAULT_CONTEXT, ...initialContext }, preserveActionOrder: true, predictableActionArguments: true, id: 'DatasetsSelector', - initial: 'closed', + type: 'parallel', states: { - closed: { - id: 'closed', - on: { - TOGGLE: 'open.hist', - }, - }, - open: { - initial: 'listingIntegrations', - on: { - CLOSE: 'closed', - TOGGLE: 'closed', - }, + popover: { + initial: 'closed', states: { - hist: { - type: 'history', + closed: { + id: 'closed', + on: { + TOGGLE: 'open.hist', + }, }, - listingIntegrations: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + open: { + initial: 'listingIntegrations', on: { - CHANGE_PANEL: [ - { - cond: 'isUnmanagedStreamsId', - target: 'listingUnmanagedStreams', - }, - { - target: 'listingIntegrationStreams', + CLOSE: 'closed', + TOGGLE: 'closed', + }, + states: { + hist: { + type: 'history', + }, + listingIntegrations: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + on: { + CHANGE_PANEL: [ + { + cond: 'isUnmanagedStreamsId', + target: 'listingUnmanagedStreams', + }, + { + target: 'listingIntegrationStreams', + }, + ], + SCROLL_TO_INTEGRATIONS_BOTTOM: { + actions: 'loadMoreIntegrations', + }, + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchIntegrations'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortIntegrations'], + }, + SELECT_ALL_LOGS_DATASET: '#closed', }, - ], - SCROLL_TO_INTEGRATIONS_BOTTOM: { - actions: 'loadMoreIntegrations', }, - SEARCH_BY_NAME: { - actions: ['storeSearch', 'searchIntegrations'], + listingIntegrationStreams: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + on: { + CHANGE_PANEL: 'listingIntegrations', + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchIntegrationsStreams'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortIntegrationsStreams'], + }, + SELECT_DATASET: '#closed', + }, }, - SORT_BY_ORDER: { - actions: ['storeSearch', 'sortIntegrations'], + listingUnmanagedStreams: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + on: { + CHANGE_PANEL: 'listingIntegrations', + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchUnmanagedStreams'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortUnmanagedStreams'], + }, + SELECT_DATASET: '#closed', + }, }, }, }, - listingIntegrationStreams: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + }, + }, + selection: { + initial: 'single', + states: { + single: { on: { - CHANGE_PANEL: 'listingIntegrations', - SELECT_DATASET: { - actions: ['storeSelected', 'selectStream'], - target: '#closed', - }, - SEARCH_BY_NAME: { - actions: ['storeSearch', 'searchIntegrationsStreams'], + SELECT_ALL_LOGS_DATASET: { + actions: ['storeAllSelection', 'notifySelectionChanged'], + target: 'all', }, - SORT_BY_ORDER: { - actions: ['storeSearch', 'sortIntegrationsStreams'], + SELECT_DATASET: { + actions: ['storeSingleSelection', 'notifySelectionChanged'], }, }, }, - listingUnmanagedStreams: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'maybeRestoreSearchResult'], + all: { on: { - CHANGE_PANEL: 'listingIntegrations', SELECT_DATASET: { - actions: ['storeSelected', 'selectStream'], - target: '#closed', - }, - SEARCH_BY_NAME: { - actions: ['storeSearch', 'searchUnmanagedStreams'], - }, - SORT_BY_ORDER: { - actions: ['storeSearch', 'sortUnmanagedStreams'], + actions: ['storeSingleSelection', 'notifySelectionChanged'], + target: 'single', }, }, }, @@ -118,8 +143,11 @@ export const createPureDatasetsSelectorStateMachine = ( } return {}; }), - storeSelected: assign((_context, event) => - 'dataset' in event ? { selected: event.dataset } : {} + storeAllSelection: assign((_context) => ({ + selection: AllDatasetSelection.create(), + })), + storeSingleSelection: assign((_context, event) => + 'dataset' in event ? { selection: SingleDatasetSelection.create(event.dataset) } : {} ), retrieveSearchFromCache: assign((context, event) => 'panelId' in event @@ -150,15 +178,13 @@ export const createDatasetsSelectorStateMachine = ({ onIntegrationsStreamsSort, onUnmanagedStreamsSearch, onUnmanagedStreamsSort, - onDatasetSelected, + onSelectionChange, onUnmanagedStreamsReload, }: DatasetsSelectorStateMachineDependencies) => createPureDatasetsSelectorStateMachine(initialContext).withConfig({ actions: { - selectStream: (_context, event) => { - if ('dataset' in event) { - return onDatasetSelected(event.dataset); - } + notifySelectionChanged: (context) => { + return onSelectionChange(context.selection); }, loadMoreIntegrations: onIntegrationsLoadMore, relaodIntegrations: onIntegrationsReload, diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/types.ts b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/types.ts index 4dc717acdfc4b..17e526aea87f9 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/types.ts +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/types.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DatasetSelection, DatasetSelectionChange } from '../../../utils/dataset_selection'; import { Dataset } from '../../../../common/datasets/models/dataset'; import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets'; import { @@ -12,10 +13,10 @@ import { SearchIntegrations, } from '../../../hooks/use_integrations'; import type { IHashedCache } from '../../../../common/hashed_cache'; -import { DatasetSelectionHandler, DatasetsSelectorSearchParams, PanelId } from '../types'; +import { DatasetsSelectorSearchParams, PanelId } from '../types'; export interface DefaultDatasetsSelectorContext { - selected?: Dataset; + selection: DatasetSelection; panelId: PanelId; searchCache: IHashedCache; search: DatasetsSelectorSearchParams; @@ -23,27 +24,43 @@ export interface DefaultDatasetsSelectorContext { export type DatasetsSelectorTypestate = | { - value: 'closed'; + value: 'popover'; context: DefaultDatasetsSelectorContext; } | { - value: 'open'; + value: 'popover.closed'; context: DefaultDatasetsSelectorContext; } | { - value: { open: 'hist' }; + value: 'popover.open'; context: DefaultDatasetsSelectorContext; } | { - value: { open: 'listingIntegrations' }; + value: 'popover.open.hist'; context: DefaultDatasetsSelectorContext; } | { - value: { open: 'listingIntegrationStreams' }; + value: 'popover.open.listingIntegrations'; context: DefaultDatasetsSelectorContext; } | { - value: { open: 'listingUnmanagedStreams' }; + value: 'popover.open.listingIntegrationStreams'; + context: DefaultDatasetsSelectorContext; + } + | { + value: 'popover.open.listingUnmanagedStreams'; + context: DefaultDatasetsSelectorContext; + } + | { + value: 'selection'; + context: DefaultDatasetsSelectorContext; + } + | { + value: 'selection.single'; + context: DefaultDatasetsSelectorContext; + } + | { + value: 'selection.all'; context: DefaultDatasetsSelectorContext; }; @@ -64,6 +81,9 @@ export type DatasetsSelectorEvent = type: 'SELECT_DATASET'; dataset: Dataset; } + | { + type: 'SELECT_ALL_LOGS_DATASET'; + } | { type: 'SCROLL_TO_INTEGRATIONS_BOTTOM'; } @@ -84,7 +104,7 @@ export interface DatasetsSelectorStateMachineDependencies { onIntegrationsSort: SearchIntegrations; onIntegrationsStreamsSearch: SearchIntegrations; onIntegrationsStreamsSort: SearchIntegrations; - onDatasetSelected: DatasetSelectionHandler; + onSelectionChange: DatasetSelectionChange; onUnmanagedStreamsReload: ReloadDatasets; onUnmanagedStreamsSearch: SearchDatasets; onUnmanagedStreamsSort: SearchDatasets; diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts index d2556809302e8..ec6090cd29edd 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts @@ -24,9 +24,9 @@ export const useDatasetSelector = ({ onIntegrationsSort, onIntegrationsStreamsSearch, onIntegrationsStreamsSort, + onSelectionChange, onUnmanagedStreamsSearch, onUnmanagedStreamsSort, - onDatasetSelected, onUnmanagedStreamsReload, }: DatasetsSelectorStateMachineDependencies) => { const datasetsSelectorStateService = useInterpret(() => @@ -38,18 +38,19 @@ export const useDatasetSelector = ({ onIntegrationsSort, onIntegrationsStreamsSearch, onIntegrationsStreamsSort, + onSelectionChange, onUnmanagedStreamsSearch, onUnmanagedStreamsSort, - onDatasetSelected, onUnmanagedStreamsReload, }) ); - const isOpen = useSelector(datasetsSelectorStateService, (state) => state.matches('open')); + const isOpen = useSelector(datasetsSelectorStateService, (state) => + state.matches('popover.open') + ); const panelId = useSelector(datasetsSelectorStateService, (state) => state.context.panelId); const search = useSelector(datasetsSelectorStateService, (state) => state.context.search); - const selected = useSelector(datasetsSelectorStateService, (state) => state.context.selected); const changePanel = useCallback( (panelDetails) => @@ -70,6 +71,11 @@ export const useDatasetSelector = ({ [datasetsSelectorStateService] ); + const selectAllLogDataset = useCallback( + () => datasetsSelectorStateService.send({ type: 'SELECT_ALL_LOGS_DATASET' }), + [datasetsSelectorStateService] + ); + const selectDataset = useCallback( (dataset) => datasetsSelectorStateService.send({ type: 'SELECT_DATASET', dataset }), [datasetsSelectorStateService] @@ -95,12 +101,12 @@ export const useDatasetSelector = ({ isOpen, panelId, search, - selected, // Actions closePopover, changePanel, scrollToIntegrationsBottom, searchByName, + selectAllLogDataset, selectDataset, sortByOrder, togglePopover, diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx index 333fe27166f3c..0ed65b4e50d8b 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiButton, EuiHorizontalRule, + EuiIcon, EuiPanel, EuiPopover, EuiPopoverProps, @@ -17,7 +18,7 @@ import { } from '@elastic/eui'; import styled from '@emotion/styled'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { Dataset } from '../../../../common/datasets/models/dataset'; +import { DatasetSelection } from '../../../utils/dataset_selection'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID, selectDatasetLabel } from '../constants'; import { getPopoverButtonStyles } from '../utils'; @@ -25,16 +26,17 @@ const panelStyle = { width: DATA_VIEW_POPOVER_CONTENT_WIDTH }; interface DatasetsPopoverProps extends Omit { children: React.ReactNode; onClick: () => void; - selected?: Dataset; + selection: DatasetSelection['selection']; } export const DatasetsPopover = ({ children, onClick, - selected, + selection, ...props }: DatasetsPopoverProps) => { - const { title, parentIntegration } = selected ?? {}; + const { iconType, parentIntegration } = selection.dataset; + const title = selection.dataset.getFullTitle(); const isMobile = useIsWithinBreakpoints(['xs', 's']); const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); @@ -52,14 +54,16 @@ export const DatasetsPopover = ({ onClick={onClick} fullWidth={isMobile} > - {hasIntegration && ( + {iconType ? ( + + ) : hasIntegration ? ( - )} + ) : null} {title} } diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx index 84f5c6cb418de..418ccbefffd00 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx @@ -17,7 +17,7 @@ import { noIntegrationsLabel, } from '../constants'; -interface IntegrationsListStatusProps { +export interface IntegrationsListStatusProps { integrations: Integration[] | null; error: Error | null; onRetry: ReloadIntegrations; diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/types.ts b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/types.ts index 970c8d60ae274..50cbcf6c1c5ba 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/types.ts +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/types.ts @@ -15,18 +15,19 @@ import { SearchIntegrations, } from '../../hooks/use_integrations'; import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants'; +import type { DatasetSelection, DatasetSelectionChange } from '../../utils/dataset_selection'; export interface DatasetSelectorProps { /* The generic data stream list */ datasets: Dataset[] | null; /* Any error occurred to show when the user preview the generic data streams */ datasetsError?: Error | null; - /* The integrations list, each integration includes its data streams */ - initialSelected: Dataset; + /* The current selection instance */ + datasetSelection: DatasetSelection; /* The integrations list, each integration includes its data streams */ integrations: Integration[] | null; /* Any error occurred to show when the user preview the integrations */ - integrationsError?: Error | null; + integrationsError: Error | null; /* Flags for loading/searching integrations or data streams*/ isLoadingIntegrations: boolean; isLoadingStreams: boolean; @@ -45,8 +46,8 @@ export interface DatasetSelectorProps { onUnmanagedStreamsReload: ReloadDatasets; /* Triggered when the uncategorized streams entry is selected */ onStreamsEntryClick: LoadDatasets; - /* Triggered when a data stream entry is selected */ - onDatasetSelected: DatasetSelectionHandler; + /* Triggered when the selection is updated */ + onSelectionChange: DatasetSelectionChange; } export type PanelId = diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx index d6f563ace8907..b135c0c22202b 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx @@ -6,11 +6,24 @@ */ import React, { RefCallback } from 'react'; -import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, + EuiIcon, +} from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { Integration } from '../../../common/datasets'; -import { DATA_VIEW_POPOVER_CONTENT_WIDTH } from './constants'; +import { Dataset, Integration } from '../../../common/datasets'; +import { + DATA_VIEW_POPOVER_CONTENT_WIDTH, + uncategorizedLabel, + UNMANAGED_STREAMS_PANEL_ID, +} from './constants'; import { DatasetSelectionHandler } from './types'; +import { LoadDatasets } from '../../hooks/use_datasets'; +import { dynamic } from '../../utils/dynamic'; +import type { IntegrationsListStatusProps } from './sub_components/integrations_list_status'; + +const IntegrationsListStatus = dynamic(() => import('./sub_components/integrations_list_status')); export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, @@ -67,3 +80,28 @@ export const buildIntegrationsTree = ({ { items: [], panels: [] } ); }; + +export const createAllLogDatasetsItem = ({ onClick }: { onClick(): void }) => { + const allLogDataset = Dataset.createAllLogsDataset(); + return { + name: allLogDataset.title, + icon: allLogDataset.iconType && , + onClick, + }; +}; + +export const createUnmanagedDatasetsItem = ({ onClick }: { onClick: LoadDatasets }) => { + return { + name: uncategorizedLabel, + icon: , + onClick, + panel: UNMANAGED_STREAMS_PANEL_ID, + }; +}; + +export const createIntegrationStatusItem = (props: IntegrationsListStatusProps) => { + return { + disabled: true, + name: , + }; +}; diff --git a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_selector.tsx b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_selector.tsx index 0c689752cc731..78c70a1e5e365 100644 --- a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_selector.tsx +++ b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_selector.tsx @@ -5,28 +5,37 @@ * 2.0. */ -import React from 'react'; +/* eslint-disable react-hooks/exhaustive-deps */ + +import React, { useState } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; -import { IndexPattern } from '@kbn/io-ts-utils'; -import { Dataset } from '../../common/datasets/models/dataset'; -import { DatasetSelectionHandler, DatasetSelector } from '../components/dataset_selector'; +import { DatasetSelector } from '../components/dataset_selector'; import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets'; -import { InternalStateProvider, useDataView } from '../hooks/use_data_view'; +import { InternalStateProvider } from '../hooks/use_data_view'; import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; import { IDatasetsClient } from '../services/datasets'; +import { + AllDatasetSelection, + DatasetSelection, + DatasetSelectionChange, +} from '../utils/dataset_selection'; interface CustomDatasetSelectorProps { stateContainer: DiscoverStateContainer; } export const CustomDatasetSelector = withProviders(({ stateContainer }) => { - // Container component, here goes all the state management and custom logic usage to keep the DatasetSelector presentational. - const dataView = useDataView(); + /** + * TOREMOVE: This is a temporary workaround to control the datasetSelection value + * until we handle the restore/initialization of the dataview with https://github.com/elastic/kibana/issues/160425, + * where this value will be used to control the DatasetSelector selection with a top level state machine. + */ + const [datasetSelection, setDatasetSelection] = useState(() => + AllDatasetSelection.create() + ); - const initialSelected: Dataset = Dataset.create({ - name: dataView.getIndexPattern() as IndexPattern, - title: dataView.getName(), - }); + // Restore All dataset selection on refresh until restore from url is not available + React.useEffect(() => handleStreamSelection(datasetSelection), []); const { error: integrationsError, @@ -54,15 +63,18 @@ export const CustomDatasetSelector = withProviders(({ stateContainer }) => { * TODO: this action will be abstracted into a method of a class adapter in a follow-up PR * since we'll need to handle more actions from the stateContainer */ - const handleStreamSelection: DatasetSelectionHandler = (dataset) => { - return stateContainer.actions.onCreateDefaultAdHocDataView(dataset.toDataviewSpec()); + const handleStreamSelection: DatasetSelectionChange = (nextDatasetSelection) => { + setDatasetSelection(nextDatasetSelection); + return stateContainer.actions.onCreateDefaultAdHocDataView( + nextDatasetSelection.toDataviewSpec() + ); }; return ( { onIntegrationsSort={sortIntegrations} onIntegrationsStreamsSearch={searchIntegrationsStreams} onIntegrationsStreamsSort={sortIntegrationsStreams} - onDatasetSelected={handleStreamSelection} + onSelectionChange={handleStreamSelection} onStreamsEntryClick={loadDatasets} onUnmanagedStreamsReload={reloadDatasets} onUnmanagedStreamsSearch={searchDatasets} diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/all_dataset_selection.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/all_dataset_selection.ts new file mode 100644 index 0000000000000..ebe3254968fb0 --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/all_dataset_selection.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 { Dataset } from '../../../common/datasets'; +import { encodeDatasetSelection } from './encoding'; +import { DatasetSelectionStrategy } from './types'; + +export class AllDatasetSelection implements DatasetSelectionStrategy { + selectionType: 'all'; + selection: { + dataset: Dataset; + }; + + private constructor() { + this.selectionType = 'all'; + this.selection = { + dataset: Dataset.createAllLogsDataset(), + }; + } + + toDataviewSpec() { + const { name, title } = this.selection.dataset.toDataviewSpec(); + return { + id: this.toURLSelectionId(), + name, + title, + }; + } + + toURLSelectionId() { + return encodeDatasetSelection({ + selectionType: this.selectionType, + }); + } + + public static create() { + return new AllDatasetSelection(); + } +} diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.test.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.test.ts new file mode 100644 index 0000000000000..d88d939858d0e --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IndexPattern } from '@kbn/io-ts-utils'; +import { encodeDatasetSelection, decodeDatasetSelectionId } from './encoding'; +import { DatasetEncodingError } from './errors'; +import { DatasetSelectionPlain } from './types'; + +describe('DatasetSelection', () => { + const allDatasetSelectionPlain: DatasetSelectionPlain = { + selectionType: 'all', + }; + const encodedAllDatasetSelection = 'BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA'; + + const singleDatasetSelectionPlain: DatasetSelectionPlain = { + selectionType: 'single', + selection: { + name: 'azure', + version: '1.5.23', + dataset: { + name: 'logs-azure.activitylogs-*' as IndexPattern, + title: 'activitylogs', + }, + }, + }; + const encodedSingleDatasetSelection = + 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtFgF4CuATmAHRZzwBu8sAJ5VadAFTkANAlhRU3BPyEiQASklFS8lu0m8wrEEjTkAjBwCsHAEwBmcuvBQeKACqCADmSPJqUVUA=='; + + const invalidDatasetSelectionPlain = { + selectionType: 'single', + selection: { + dataset: { + // Missing mandatory `name` property + title: 'activitylogs', + }, + }, + }; + const invalidCompressedId = 'random'; + const invalidEncodedDatasetSelection = 'BQZwpgNmDGAuCWB7AdgFQJ4AcwC4T2QHMoBKIA=='; + + describe('#encodeDatasetSelection', () => { + test('should encode and compress a valid DatasetSelection plain object', () => { + // Encode AllDatasetSelection plain object + expect(encodeDatasetSelection(allDatasetSelectionPlain)).toEqual(encodedAllDatasetSelection); + // Encode SingleDatasetSelection plain object + expect(encodeDatasetSelection(singleDatasetSelectionPlain)).toEqual( + encodedSingleDatasetSelection + ); + }); + + test('should throw a DatasetEncodingError if the input is an invalid DatasetSelection plain object', () => { + const encodingRunner = () => + encodeDatasetSelection(invalidDatasetSelectionPlain as DatasetSelectionPlain); + + expect(encodingRunner).toThrow(DatasetEncodingError); + expect(encodingRunner).toThrow(/^The current dataset selection is invalid/); + }); + }); + + describe('#decodeDatasetSelectionId', () => { + test('should decode and decompress a valid encoded string', () => { + // Decode AllDatasetSelection plain object + expect(decodeDatasetSelectionId(encodedAllDatasetSelection)).toEqual( + allDatasetSelectionPlain + ); + // Decode SingleDatasetSelection plain object + expect(decodeDatasetSelectionId(encodedSingleDatasetSelection)).toEqual( + singleDatasetSelectionPlain + ); + }); + + test('should throw a DatasetEncodingError if the input is an invalid compressed id', () => { + expect(() => decodeDatasetSelectionId(invalidCompressedId)).toThrow( + new DatasetEncodingError('The stored id is not a valid compressed value.') + ); + }); + + test('should throw a DatasetEncodingError if the decompressed value is an invalid DatasetSelection plain object', () => { + const decodingRunner = () => decodeDatasetSelectionId(invalidEncodedDatasetSelection); + + expect(decodingRunner).toThrow(DatasetEncodingError); + expect(decodingRunner).toThrow(/^The current dataset selection is invalid/); + }); + }); + + test('encoding and decoding should restore the original DatasetSelection plain object', () => { + // Encode/Decode AllDatasetSelection plain object + expect(decodeDatasetSelectionId(encodeDatasetSelection(allDatasetSelectionPlain))).toEqual( + allDatasetSelectionPlain + ); + // Encode/Decode SingleDatasetSelection plain object + expect(decodeDatasetSelectionId(encodeDatasetSelection(singleDatasetSelectionPlain))).toEqual( + singleDatasetSelectionPlain + ); + }); +}); diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.ts new file mode 100644 index 0000000000000..5a84c398e7d4e --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/encoding.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { decode, encode, RisonValue } from '@kbn/rison'; +import * as lz from 'lz-string'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { DatasetEncodingError } from './errors'; +import { DatasetSelectionPlain, datasetSelectionPlainRT } from './types'; + +export const encodeDatasetSelection = (datasetSelectionPlain: DatasetSelectionPlain) => { + const safeDatasetSelection = decodeOrThrow( + datasetSelectionPlainRT, + (message: string) => + new DatasetEncodingError(`The current dataset selection is invalid: ${message}"`) + )(datasetSelectionPlain); + + return lz.compressToBase64(encode(safeDatasetSelection)); +}; + +export const decodeDatasetSelectionId = (datasetSelectionId: string): DatasetSelectionPlain => { + const risonDatasetSelection: RisonValue = lz.decompressFromBase64(datasetSelectionId); + + if (risonDatasetSelection === null || risonDatasetSelection === '') { + throw new DatasetEncodingError('The stored id is not a valid compressed value.'); + } + + const decodedDatasetSelection = decode(risonDatasetSelection); + + const datasetSelection = decodeOrThrow( + datasetSelectionPlainRT, + (message: string) => + new DatasetEncodingError(`The current dataset selection is invalid: ${message}"`) + )(decodedDatasetSelection); + + return datasetSelection; +}; diff --git a/x-pack/plugins/infra/server/services/log_views/errors.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/errors.ts similarity index 73% rename from x-pack/plugins/infra/server/services/log_views/errors.ts rename to x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/errors.ts index fb0dc3b031511..748a4b91b246c 100644 --- a/x-pack/plugins/infra/server/services/log_views/errors.ts +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/errors.ts @@ -5,9 +5,10 @@ * 2.0. */ -export class NotFoundError extends Error { - constructor(message?: string) { +export class DatasetEncodingError extends Error { + constructor(message: string) { super(message); Object.setPrototypeOf(this, new.target.prototype); + this.name = 'DatasetEncodingError'; } } diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts new file mode 100644 index 0000000000000..8a855a46412f3 --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AllDatasetSelection } from './all_dataset_selection'; +import { SingleDatasetSelection } from './single_dataset_selection'; +import { DatasetSelectionPlain } from './types'; + +export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) => { + if (datasetSelection.selectionType === 'all') { + return AllDatasetSelection.create(); + } + if (datasetSelection.selectionType === 'single') { + return SingleDatasetSelection.fromSelection(datasetSelection.selection); + } +}; diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/index.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/index.ts new file mode 100644 index 0000000000000..24a3bad6605e3 --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/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 { AllDatasetSelection } from './all_dataset_selection'; +import { SingleDatasetSelection } from './single_dataset_selection'; + +export type DatasetSelection = AllDatasetSelection | SingleDatasetSelection; +export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void; + +export * from './all_dataset_selection'; +export * from './single_dataset_selection'; +export * from './encoding'; +export * from './hydrate_dataset_selection.ts'; +export * from './types'; diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/single_dataset_selection.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/single_dataset_selection.ts new file mode 100644 index 0000000000000..ce64a3a45084c --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/single_dataset_selection.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 { Dataset } from '../../../common/datasets'; +import { encodeDatasetSelection } from './encoding'; +import { DatasetSelectionStrategy, SingleDatasetSelectionPayload } from './types'; + +export class SingleDatasetSelection implements DatasetSelectionStrategy { + selectionType: 'single'; + selection: { + name?: string; + version?: string; + dataset: Dataset; + }; + + private constructor(dataset: Dataset) { + this.selectionType = 'single'; + this.selection = { + name: dataset.parentIntegration?.name, + version: dataset.parentIntegration?.version, + dataset, + }; + } + + toDataviewSpec() { + const { name, title } = this.selection.dataset.toDataviewSpec(); + return { + id: this.toURLSelectionId(), + name, + title, + }; + } + + toURLSelectionId() { + return encodeDatasetSelection({ + selectionType: this.selectionType, + selection: { + name: this.selection.name, + version: this.selection.version, + dataset: this.selection.dataset.toPlain(), + }, + }); + } + + public static fromSelection(selection: SingleDatasetSelectionPayload) { + const { name, version, dataset } = selection; + + // Attempt reconstructing the integration object + const integration = name && version ? { name, version } : undefined; + const datasetInstance = Dataset.create(dataset, integration); + + return new SingleDatasetSelection(datasetInstance); + } + + public static create(dataset: Dataset) { + return new SingleDatasetSelection(dataset); + } +} diff --git a/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/types.ts b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/types.ts new file mode 100644 index 0000000000000..6608a29acc821 --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/public/utils/dataset_selection/types.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { DataViewSpec } from '@kbn/data-views-plugin/common'; +import * as rt from 'io-ts'; +import { datasetRT } from '../../../common/datasets'; + +export const allDatasetSelectionPlainRT = rt.type({ + selectionType: rt.literal('all'), +}); + +const integrationNameRT = rt.partial({ + name: rt.string, +}); + +const integrationVersionRT = rt.partial({ + version: rt.string, +}); + +const singleDatasetSelectionPayloadRT = rt.intersection([ + integrationNameRT, + integrationVersionRT, + rt.type({ + dataset: datasetRT, + }), +]); + +export const singleDatasetSelectionPlainRT = rt.type({ + selectionType: rt.literal('single'), + selection: singleDatasetSelectionPayloadRT, +}); + +export const datasetSelectionPlainRT = rt.union([ + allDatasetSelectionPlainRT, + singleDatasetSelectionPlainRT, +]); + +export type SingleDatasetSelectionPayload = rt.TypeOf; +export type DatasetSelectionPlain = rt.TypeOf; + +export interface DatasetSelectionStrategy { + toDataviewSpec(): DataViewSpec; + toURLSelectionId(): string; +} diff --git a/x-pack/plugins/discover_log_explorer/tsconfig.json b/x-pack/plugins/discover_log_explorer/tsconfig.json index 8844db22796e5..babab6c97b029 100644 --- a/x-pack/plugins/discover_log_explorer/tsconfig.json +++ b/x-pack/plugins/discover_log_explorer/tsconfig.json @@ -13,6 +13,7 @@ "@kbn/kibana-utils-plugin", "@kbn/io-ts-utils", "@kbn/data-views-plugin", + "@kbn/rison", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 38d9f256d0d4f..88290c2ed4831 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -150,7 +150,7 @@ export const APPLICATIONS_PLUGIN = { export const VECTOR_SEARCH_PLUGIN = { DESCRIPTION: i18n.translate('xpack.enterpriseSearch.vectorSearch.description', { defaultMessage: - 'Elasticsearch can be used as a vector database and search along with other semantic search methods.', + 'Elasticsearch can be used as a vector database, which enables vector search and semantic search use cases.', }), ID: 'enterpriseSearchVectorSearch', LOGO: 'logoEnterpriseSearch', @@ -210,3 +210,5 @@ export const DEFAULT_PRODUCT_FEATURES: ProductFeatures = { hasNativeConnectors: true, hasWebCrawler: true, }; + +export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; diff --git a/x-pack/plugins/enterprise_search/common/types/kibana_deps.ts b/x-pack/plugins/enterprise_search/common/types/kibana_deps.ts index 2bc2fed33aa4f..1e268aad1f73c 100644 --- a/x-pack/plugins/enterprise_search/common/types/kibana_deps.ts +++ b/x-pack/plugins/enterprise_search/common/types/kibana_deps.ts @@ -11,7 +11,6 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { FeaturesPluginStart } from '@kbn/features-plugin/public'; import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; -import type { InfraClientStartExports } from '@kbn/infra-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SecurityPluginStart } from '@kbn/security-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -24,7 +23,6 @@ export interface KibanaDeps { discover: DiscoverStart; features: FeaturesPluginStart; guidedOnboarding: GuidedOnboardingPluginStart; - infra: InfraClientStartExports; licensing: LicensingPluginStart; security: SecurityPluginStart; share: SharePluginStart; diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 0a6d3ebee52ae..01e43542e84f7 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -17,7 +17,7 @@ "data", "discover", "charts", - "infra", + "logsShared", "cloud", "esUiShared", "guidedOnboarding", diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx index 81bd97840b190..9cd4802ed2be4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx @@ -111,7 +111,7 @@ export const ConnectorConfiguration: React.FC = () => { @@ -180,7 +180,7 @@ service_type: "${index.connector.service_type || 'changeme'}" title: i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.deployConnector.title', { - defaultMessage: 'Deploy a connector', + defaultMessage: 'Deploy connector', } ), titleSize: 'xs', @@ -325,7 +325,7 @@ service_type: "${index.connector.service_type || 'changeme'}" 'xpack.enterpriseSearch.content.indices.configurationConnector.support.description', { defaultMessage: - 'Your connector will have to be deployed to your own infrastructure.', + 'You need to deploy this connector on your own infrastructure.', } )} @@ -345,7 +345,7 @@ service_type: "${index.connector.service_type || 'changeme'}" {i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.support.manageKeys.label', { - defaultMessage: 'Manage keys', + defaultMessage: 'Manage API keys', } )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/connector_cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/connector_cron_editor.tsx index 46eccba6a4f70..9c38bdea220ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/connector_cron_editor.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/connector_cron_editor.tsx @@ -135,7 +135,7 @@ function cronToFrequency(cron: string): Frequency { if (fields.length < 4) { return 'YEAR'; } - if (fields[1] === '*' || fields[1].startsWith('*/')) { + if (fields[1] === '*' || fields[1].includes(',')) { return 'MINUTE'; } if (fields[2] === '*') { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx index 8cf25e16426a8..ea1b1aaecd23f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx @@ -20,6 +20,8 @@ import { import { i18n } from '@kbn/i18n'; +import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '../../../../../common/constants'; + import { KibanaLogic } from '../../../shared/kibana'; import { @@ -44,7 +46,7 @@ export const SearchIndexDocuments: React.FC = () => { const indexToShow = selectedIndexType === 'content-index' ? indexName - : indexName.replace('search-', '.search-acl-filter-'); + : indexName.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX); const shouldShowAccessControlSwitcher = hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_mappings.tsx index 4bc77672f5f7b..5ec31986b2fe4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_mappings.tsx @@ -25,6 +25,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '../../../../../common/constants'; + import { docLinks } from '../../../shared/doc_links'; import { KibanaLogic } from '../../../shared/kibana'; @@ -51,7 +53,7 @@ export const SearchIndexIndexMappings: React.FC = () => { const indexToShow = selectedIndexType === 'content-index' ? indexName - : indexName.replace('search-', '.search-acl-filter-'); + : indexName.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX); const shouldShowAccessControlSwitch = hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled; diff --git a/x-pack/plugins/enterprise_search/public/applications/esre/components/esre_guide/esre_docs_section.tsx b/x-pack/plugins/enterprise_search/public/applications/esre/components/esre_guide/esre_docs_section.tsx index df2bebd9fbfcc..279d707555cde 100644 --- a/x-pack/plugins/enterprise_search/public/applications/esre/components/esre_guide/esre_docs_section.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/esre/components/esre_guide/esre_docs_section.tsx @@ -77,7 +77,7 @@ export const EsreDocsSection: React.FC = () => (

{i18n.translate('xpack.enterpriseSearch.esre.rrfRankingPanel.step1.rrfDocsLinkText', { - defaultMessage: 'Reciprocal Rank Fusion documentations', + defaultMessage: 'Reciprocal Rank Fusion documentation', })} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/__snapshots__/cron_editor.test.tsx.snap b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/__snapshots__/cron_editor.test.tsx.snap index 2e22e3bfe30b0..d490f4f87057d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/__snapshots__/cron_editor.test.tsx.snap +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/__snapshots__/cron_editor.test.tsx.snap @@ -3391,241 +3391,21 @@ exports[`CronEditor is rendered with a MINUTE frequency 1`] = ` minute="10" minuteOptions={ Array [ - Object { - "text": "1", - "value": "*/1", - }, - Object { - "text": "2", - "value": "*/2", - }, - Object { - "text": "3", - "value": "*/3", - }, - Object { - "text": "4", - "value": "*/4", - }, Object { "text": "5", - "value": "*/5", - }, - Object { - "text": "6", - "value": "*/6", - }, - Object { - "text": "7", - "value": "*/7", - }, - Object { - "text": "8", - "value": "*/8", - }, - Object { - "text": "9", - "value": "*/9", + "value": "0,5,10,15,20,25,30,35,40,45,50,55", }, Object { "text": "10", - "value": "*/10", - }, - Object { - "text": "11", - "value": "*/11", - }, - Object { - "text": "12", - "value": "*/12", - }, - Object { - "text": "13", - "value": "*/13", - }, - Object { - "text": "14", - "value": "*/14", + "value": "0,10,20,30,40,50", }, Object { "text": "15", - "value": "*/15", - }, - Object { - "text": "16", - "value": "*/16", - }, - Object { - "text": "17", - "value": "*/17", - }, - Object { - "text": "18", - "value": "*/18", - }, - Object { - "text": "19", - "value": "*/19", - }, - Object { - "text": "20", - "value": "*/20", - }, - Object { - "text": "21", - "value": "*/21", - }, - Object { - "text": "22", - "value": "*/22", - }, - Object { - "text": "23", - "value": "*/23", - }, - Object { - "text": "24", - "value": "*/24", - }, - Object { - "text": "25", - "value": "*/25", - }, - Object { - "text": "26", - "value": "*/26", - }, - Object { - "text": "27", - "value": "*/27", - }, - Object { - "text": "28", - "value": "*/28", - }, - Object { - "text": "29", - "value": "*/29", + "value": "0,15,30,45", }, Object { "text": "30", - "value": "*/30", - }, - Object { - "text": "31", - "value": "*/31", - }, - Object { - "text": "32", - "value": "*/32", - }, - Object { - "text": "33", - "value": "*/33", - }, - Object { - "text": "34", - "value": "*/34", - }, - Object { - "text": "35", - "value": "*/35", - }, - Object { - "text": "36", - "value": "*/36", - }, - Object { - "text": "37", - "value": "*/37", - }, - Object { - "text": "38", - "value": "*/38", - }, - Object { - "text": "39", - "value": "*/39", - }, - Object { - "text": "40", - "value": "*/40", - }, - Object { - "text": "41", - "value": "*/41", - }, - Object { - "text": "42", - "value": "*/42", - }, - Object { - "text": "43", - "value": "*/43", - }, - Object { - "text": "44", - "value": "*/44", - }, - Object { - "text": "45", - "value": "*/45", - }, - Object { - "text": "46", - "value": "*/46", - }, - Object { - "text": "47", - "value": "*/47", - }, - Object { - "text": "48", - "value": "*/48", - }, - Object { - "text": "49", - "value": "*/49", - }, - Object { - "text": "50", - "value": "*/50", - }, - Object { - "text": "51", - "value": "*/51", - }, - Object { - "text": "52", - "value": "*/52", - }, - Object { - "text": "53", - "value": "*/53", - }, - Object { - "text": "54", - "value": "*/54", - }, - Object { - "text": "55", - "value": "*/55", - }, - Object { - "text": "56", - "value": "*/56", - }, - Object { - "text": "57", - "value": "*/57", - }, - Object { - "text": "58", - "value": "*/58", - }, - Object { - "text": "59", - "value": "*/59", + "value": "0,30", }, ] } @@ -3690,241 +3470,21 @@ exports[`CronEditor is rendered with a MINUTE frequency 1`] = ` onFocus={[Function]} options={ Array [ - Object { - "text": "1", - "value": "*/1", - }, - Object { - "text": "2", - "value": "*/2", - }, - Object { - "text": "3", - "value": "*/3", - }, - Object { - "text": "4", - "value": "*/4", - }, Object { "text": "5", - "value": "*/5", - }, - Object { - "text": "6", - "value": "*/6", - }, - Object { - "text": "7", - "value": "*/7", - }, - Object { - "text": "8", - "value": "*/8", - }, - Object { - "text": "9", - "value": "*/9", + "value": "0,5,10,15,20,25,30,35,40,45,50,55", }, Object { "text": "10", - "value": "*/10", - }, - Object { - "text": "11", - "value": "*/11", - }, - Object { - "text": "12", - "value": "*/12", - }, - Object { - "text": "13", - "value": "*/13", - }, - Object { - "text": "14", - "value": "*/14", + "value": "0,10,20,30,40,50", }, Object { "text": "15", - "value": "*/15", - }, - Object { - "text": "16", - "value": "*/16", - }, - Object { - "text": "17", - "value": "*/17", - }, - Object { - "text": "18", - "value": "*/18", - }, - Object { - "text": "19", - "value": "*/19", - }, - Object { - "text": "20", - "value": "*/20", - }, - Object { - "text": "21", - "value": "*/21", - }, - Object { - "text": "22", - "value": "*/22", - }, - Object { - "text": "23", - "value": "*/23", - }, - Object { - "text": "24", - "value": "*/24", - }, - Object { - "text": "25", - "value": "*/25", - }, - Object { - "text": "26", - "value": "*/26", - }, - Object { - "text": "27", - "value": "*/27", - }, - Object { - "text": "28", - "value": "*/28", - }, - Object { - "text": "29", - "value": "*/29", + "value": "0,15,30,45", }, Object { "text": "30", - "value": "*/30", - }, - Object { - "text": "31", - "value": "*/31", - }, - Object { - "text": "32", - "value": "*/32", - }, - Object { - "text": "33", - "value": "*/33", - }, - Object { - "text": "34", - "value": "*/34", - }, - Object { - "text": "35", - "value": "*/35", - }, - Object { - "text": "36", - "value": "*/36", - }, - Object { - "text": "37", - "value": "*/37", - }, - Object { - "text": "38", - "value": "*/38", - }, - Object { - "text": "39", - "value": "*/39", - }, - Object { - "text": "40", - "value": "*/40", - }, - Object { - "text": "41", - "value": "*/41", - }, - Object { - "text": "42", - "value": "*/42", - }, - Object { - "text": "43", - "value": "*/43", - }, - Object { - "text": "44", - "value": "*/44", - }, - Object { - "text": "45", - "value": "*/45", - }, - Object { - "text": "46", - "value": "*/46", - }, - Object { - "text": "47", - "value": "*/47", - }, - Object { - "text": "48", - "value": "*/48", - }, - Object { - "text": "49", - "value": "*/49", - }, - Object { - "text": "50", - "value": "*/50", - }, - Object { - "text": "51", - "value": "*/51", - }, - Object { - "text": "52", - "value": "*/52", - }, - Object { - "text": "53", - "value": "*/53", - }, - Object { - "text": "54", - "value": "*/54", - }, - Object { - "text": "55", - "value": "*/55", - }, - Object { - "text": "56", - "value": "*/56", - }, - Object { - "text": "57", - "value": "*/57", - }, - Object { - "text": "58", - "value": "*/58", - }, - Object { - "text": "59", - "value": "*/59", + "value": "0,30", }, ] } @@ -3970,358 +3530,28 @@ exports[`CronEditor is rendered with a MINUTE frequency 1`] = ` > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ({ - value: '*/' + value.toString(), - text: value.toString(), -})); +export const EVERY_MINUTE_OPTIONS = [ + { + text: '5', + value: '0,5,10,15,20,25,30,35,40,45,50,55', + }, + { + text: '10', + value: '0,10,20,30,40,50', + }, + { + text: '15', + value: '0,15,30,45', + }, + { + text: '30', + value: '0,30', + }, +]; export const MINUTE_OPTIONS = makeSequence(0, 59).map((value) => ({ value: value.toString(), @@ -111,7 +125,7 @@ export const frequencyToFieldsMap: Record = { export const frequencyToBaselineFieldsMap: Record = { MINUTE: { second: '0', - minute: '*/1', + minute: '0,5,10,15,20,25,30,35,40,45,50,55', hour: '*', date: '*', month: '*', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx index bcda5c37f33c7..ed27a69085d97 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx @@ -29,7 +29,6 @@ import { CronMonthly } from './cron_monthly'; import { CronWeekly } from './cron_weekly'; import { CronYearly } from './cron_yearly'; import { cronExpressionToParts, cronPartsToExpression } from './services'; -import { convertFromEveryXMinute, convertToEveryXMinute } from './services/cron'; import { Frequency, Field, FieldToValueMap } from './types'; const excludeBlockListedFrequencies = ( @@ -80,20 +79,14 @@ export class CronEditor extends Component { } onChangeFrequency = (frequency: Frequency) => { - const { onChange, fieldToPreferredValueMap, frequency: oldFrequency } = this.props; + const { onChange, fieldToPreferredValueMap } = this.props; // Update fields which aren't editable with acceptable baseline values. const editableFields = Object.keys(frequencyToFieldsMap[frequency]) as Field[]; const inheritedFields = editableFields.reduce( (fieldBaselines, field) => { if (fieldToPreferredValueMap[field] != null) { - if (oldFrequency === 'MINUTE') { - fieldBaselines[field] = convertFromEveryXMinute(fieldToPreferredValueMap[field]); - } else if (frequency === 'MINUTE') { - fieldBaselines[field] = convertToEveryXMinute(fieldToPreferredValueMap[field]); - } else { - fieldBaselines[field] = fieldToPreferredValueMap[field]; - } + fieldBaselines[field] = fieldToPreferredValueMap[field]; } return fieldBaselines; }, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts index fc2019d63c17d..542502fbcbe76 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts @@ -56,18 +56,3 @@ export function cronPartsToExpression({ }: FieldToValueMap): string { return `${second} ${minute} ${hour} ${date} ${month} ${day}`; } - -export function convertToEveryXMinute( - minute: FieldToValueMap['minute'] -): FieldToValueMap['minute'] { - if (!minute) return minute; - if (minute.startsWith('*/')) return minute; - return '*/' + minute; -} - -export function convertFromEveryXMinute( - minute: FieldToValueMap['minute'] -): FieldToValueMap['minute'] { - if (!minute) return minute; - return minute.startsWith('*/') ? minute.slice(2) : minute; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx index d655a7c57a971..5f3af9a500b9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx @@ -7,17 +7,17 @@ import React from 'react'; -import { LogStream, LogStreamProps } from '@kbn/infra-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { LogStream, LogStreamProps } from '@kbn/logs-shared-plugin/public'; /* - * EnterpriseSearchLogStream is a light wrapper on top of infra's embeddable LogStream component. + * EnterpriseSearchLogStream is a light wrapper on top of logsShared's embeddable LogStream component. * It prepopulates our log source ID (set in server/plugin.ts) and sets a basic 24-hours-ago * default for timestamps. All other props get passed as-is to the underlying LogStream. * * Documentation links for reference: - * - https://github.com/elastic/kibana/blob/main/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx - * - Run `yarn storybook infra` for live docs + * - https://github.com/elastic/kibana/blob/main/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.mdx + * - Run `yarn storybook logsShared` for live docs */ interface Props extends Omit { diff --git a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx index 82b067962e448..1c14178b1583b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx @@ -87,12 +87,12 @@ export const VectorSearchGuide: React.FC = () => {

{' '}

@@ -216,7 +216,7 @@ export const VectorSearchGuide: React.FC = () => { description={ } /> @@ -235,7 +235,7 @@ export const VectorSearchGuide: React.FC = () => { description={ } /> diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts index 704595b708c5e..2918641814341 100644 --- a/x-pack/plugins/enterprise_search/server/index.ts +++ b/x-pack/plugins/enterprise_search/server/index.ts @@ -53,4 +53,3 @@ export const CURRENT_CONNECTORS_INDEX = '.elastic-connectors-v1'; export const CONNECTORS_JOBS_INDEX = '.elastic-connectors-sync-jobs'; export const CONNECTORS_VERSION = 1; export const CRAWLERS_INDEX = '.ent-search-actastic-crawler2_configurations_v2'; -export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts index ca7ae9fc76a66..4e35acd1434b4 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts @@ -7,11 +7,9 @@ import { IScopedClusterClient } from '@kbn/core/server'; -import { - CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX, - CONNECTORS_INDEX, - CONNECTORS_JOBS_INDEX, -} from '../..'; +import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..'; +import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '../../../common/constants'; + import { SyncJobType, SyncStatus, TriggerMethod } from '../../../common/types/connectors'; import { ErrorCode } from '../../../common/types/error_codes'; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts index e4ef3288819f6..a3b2164c467a1 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts @@ -7,14 +7,13 @@ import { IScopedClusterClient } from '@kbn/core/server'; -import { - CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX, - CONNECTORS_INDEX, - CONNECTORS_JOBS_INDEX, -} from '../..'; +import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..'; import { isConfigEntry } from '../../../common/connectors/is_category_entry'; -import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../common/constants'; +import { + CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX, + ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, +} from '../../../common/constants'; import { ConnectorConfiguration, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/delete_access_control_index.ts b/x-pack/plugins/enterprise_search/server/lib/indices/delete_access_control_index.ts new file mode 100644 index 0000000000000..3d185c9fde6f7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/delete_access_control_index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isIndexNotFoundException } from '@kbn/core-saved-objects-migration-server-internal'; +import { IScopedClusterClient } from '@kbn/core/server'; + +import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '../../../common/constants'; + +export const deleteAccessControlIndex = async (client: IScopedClusterClient, index: string) => { + try { + await client.asCurrentUser.indices.delete({ + index: index.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX), + }); + } catch (e) { + // Gracefully exit if index not found. This is a valid case. + if (!isIndexNotFoundException(e)) throw e; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index b072fbdf51503..041a295ff4831 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -21,7 +21,7 @@ import { DataPluginStart } from '@kbn/data-plugin/server/plugin'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; import { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; -import { InfraPluginSetup } from '@kbn/infra-plugin/server'; +import { LogsSharedPluginSetup } from '@kbn/logs-shared-plugin/server'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; @@ -88,7 +88,7 @@ interface PluginsSetup { features: FeaturesPluginSetup; globalSearch: GlobalSearchPluginSetup; guidedOnboarding: GuidedOnboardingPluginSetup; - infra: InfraPluginSetup; + logsShared: LogsSharedPluginSetup; ml?: MlPluginSetup; security: SecurityPluginSetup; usageCollection?: UsageCollectionSetup; @@ -125,7 +125,7 @@ export class EnterpriseSearchPlugin implements Plugin { security, features, globalSearch, - infra, + logsShared, customIntegrations, ml, guidedOnboarding, @@ -261,9 +261,9 @@ export class EnterpriseSearchPlugin implements Plugin { /* * Register logs source configuration, used by LogStream components - * @see https://github.com/elastic/kibana/blob/main/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx#with-a-source-configuration + * @see https://github.com/elastic/kibana/blob/main/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.mdx#with-a-source-configuration */ - infra.logViews.defineInternalLogView(ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID, { + logsShared.logViews.defineInternalLogView(ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID, { logIndices: { indexName: 'logs-app_search.search_relevance_suggestions-*', type: 'index_name', @@ -271,7 +271,7 @@ export class EnterpriseSearchPlugin implements Plugin { name: 'Enterprise Search Search Relevance Logs', }); - infra.logViews.defineInternalLogView(ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID, { + logsShared.logViews.defineInternalLogView(ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID, { logIndices: { indexName: 'logs-enterprise_search*', type: 'index_name', @@ -279,7 +279,7 @@ export class EnterpriseSearchPlugin implements Plugin { name: 'Enterprise Search Audit Logs', }); - infra.logViews.defineInternalLogView(ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID, { + logsShared.logViews.defineInternalLogView(ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID, { logIndices: { indexName: 'behavioral_analytics-events-*', type: 'index_name', diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index 81233d842d629..5b5a0c7e99b9b 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -26,6 +26,7 @@ import { fetchConnectorByIndexName, fetchConnectors } from '../../lib/connectors import { fetchCrawlerByIndexName, fetchCrawlers } from '../../lib/crawler/fetch_crawlers'; import { createIndex } from '../../lib/indices/create_index'; +import { deleteAccessControlIndex } from '../../lib/indices/delete_access_control_index'; import { indexOrAliasExists } from '../../lib/indices/exists_index'; import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices, fetchSearchIndices } from '../../lib/indices/fetch_indices'; @@ -201,6 +202,7 @@ export function registerIndexRoutes({ } await deleteIndexPipelines(client, indexName); + await deleteAccessControlIndex(client, indexName); await client.asCurrentUser.indices.delete({ index: indexName }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts index b963157694acf..0ce48c33a9d38 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts @@ -7,9 +7,13 @@ import { schema } from '@kbn/config-schema'; +import { ErrorCode } from '../../../common/types/error_codes'; + import { fetchMapping } from '../../lib/fetch_mapping'; import { RouteDependencies } from '../../plugin'; +import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { isIndexNotFoundException } from '../../utils/identify_exceptions'; export function registerMappingRoute({ router, log }: RouteDependencies) { router.get( @@ -24,12 +28,24 @@ export function registerMappingRoute({ router, log }: RouteDependencies) { elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const mapping = await fetchMapping(client, request.params.index_name); + try { + const mapping = await fetchMapping(client, request.params.index_name); - return response.ok({ - body: mapping, - headers: { 'content-type': 'application/json' }, - }); + return response.ok({ + body: mapping, + headers: { 'content-type': 'application/json' }, + }); + } catch (error) { + if (isIndexNotFoundException(error)) { + return createError({ + errorCode: ErrorCode.INDEX_NOT_FOUND, + message: 'Could not found index', + response, + statusCode: 404, + }); + } + throw error; + } }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts index 3ea14a2013a59..40158204e3ea3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts @@ -10,10 +10,13 @@ import { SearchResponseBody } from '@elastic/elasticsearch/lib/api/types'; import { schema } from '@kbn/config-schema'; import { ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } from '../../../common/constants'; +import { ErrorCode } from '../../../common/types/error_codes'; import { fetchSearchResults } from '../../lib/fetch_search_results'; import { RouteDependencies } from '../../plugin'; +import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { isIndexNotFoundException } from '../../utils/identify_exceptions'; const calculateMeta = (searchResults: SearchResponseBody, page: number, size: number) => { let totalResults = 0; @@ -64,21 +67,33 @@ export function registerSearchRoute({ router, log }: RouteDependencies) { const { client } = (await context.core).elasticsearch; const { page = 0, size = ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } = request.query; const from = page * size; - const searchResults: SearchResponseBody = await fetchSearchResults( - client, - indexName, - searchQuery, - from, - size - ); + try { + const searchResults: SearchResponseBody = await fetchSearchResults( + client, + indexName, + searchQuery, + from, + size + ); - return response.ok({ - body: { - meta: calculateMeta(searchResults, page, size), - results: searchResults, - }, - headers: { 'content-type': 'application/json' }, - }); + return response.ok({ + body: { + meta: calculateMeta(searchResults, page, size), + results: searchResults, + }, + headers: { 'content-type': 'application/json' }, + }); + } catch (error) { + if (isIndexNotFoundException(error)) { + return createError({ + errorCode: ErrorCode.INDEX_NOT_FOUND, + message: 'Could not found index', + response, + statusCode: 404, + }); + } + throw error; + } }) ); } diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index 0ea008f3691f5..39758cb511103 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -22,7 +22,6 @@ "@kbn/usage-collection-plugin", "@kbn/cloud-plugin", "@kbn/cloud-chat-plugin", - "@kbn/infra-plugin", "@kbn/features-plugin", "@kbn/lens-plugin", "@kbn/licensing-plugin", @@ -60,6 +59,8 @@ "@kbn/core-elasticsearch-server-mocks", "@kbn/shared-ux-link-redirect-app", "@kbn/global-search-plugin", + "@kbn/logs-shared-plugin", "@kbn/share-plugin", + "@kbn/core-saved-objects-migration-server-internal", ] } diff --git a/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.test.ts b/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.test.ts new file mode 100644 index 0000000000000..3b4c340488572 --- /dev/null +++ b/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.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 type { ChromeBreadcrumb } from '@kbn/core/public'; +import { emptyLastBreadcrumbUrl } from './breadcrumbs'; + +describe('emptyLastBreadcrumbUrl', () => { + it('should empty the URL and onClick function of the last breadcrumb', () => { + const breadcrumbs: ChromeBreadcrumb[] = [ + { text: 'Home', href: '/home', onClick: () => {} }, + { text: 'Breadcrumb 1', href: '/bc1', onClick: () => {} }, + { text: 'Last Breadcrumbs', href: '/last_bc', onClick: () => {} }, + ]; + + const expectedBreadcrumbs = [ + { text: 'Home', href: '/home', onClick: breadcrumbs[0].onClick }, + { text: 'Breadcrumb 1', href: '/bc1', onClick: breadcrumbs[1].onClick }, + { text: 'Last Breadcrumbs', href: '', onClick: undefined }, + ]; + + expect(emptyLastBreadcrumbUrl(breadcrumbs)).toEqual(expectedBreadcrumbs); + }); + + it('should return the original breadcrumbs if the input is empty', () => { + const emptyBreadcrumbs: ChromeBreadcrumb[] = []; + + expect(emptyLastBreadcrumbUrl(emptyBreadcrumbs)).toEqual(emptyBreadcrumbs); + }); +}); diff --git a/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.ts b/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.ts new file mode 100644 index 0000000000000..8fa8107226f4c --- /dev/null +++ b/x-pack/plugins/ess_security/public/breadcrumbs/breadcrumbs.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginStart as SecuritySolutionPluginStart } from '@kbn/security-solution-plugin/public'; +import { ChromeBreadcrumb, CoreStart } from '@kbn/core/public'; + +export const subscribeBreadcrumbs = ( + securitySolution: SecuritySolutionPluginStart, + core: CoreStart +) => { + securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => { + const breadcrumbs = [...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]; + if (breadcrumbs.length > 0) { + core.chrome.setBreadcrumbs(emptyLastBreadcrumbUrl(breadcrumbs)); + } + }); +}; + +export const emptyLastBreadcrumbUrl = (breadcrumbs: ChromeBreadcrumb[]) => { + const lastBreadcrumb = breadcrumbs[breadcrumbs.length - 1]; + if (lastBreadcrumb) { + return [...breadcrumbs.slice(0, -1), { ...lastBreadcrumb, href: '', onClick: undefined }]; + } + return breadcrumbs; +}; diff --git a/x-pack/plugins/ess_security/public/breadcrumbs/index.ts b/x-pack/plugins/ess_security/public/breadcrumbs/index.ts new file mode 100644 index 0000000000000..e1ed7fcff0986 --- /dev/null +++ b/x-pack/plugins/ess_security/public/breadcrumbs/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { subscribeBreadcrumbs } from './breadcrumbs'; diff --git a/x-pack/plugins/ess_security/public/common/jest.config.js b/x-pack/plugins/ess_security/public/common/jest.config.js deleted file mode 100644 index ae6cd807e4cc1..0000000000000 --- a/x-pack/plugins/ess_security/public/common/jest.config.js +++ /dev/null @@ -1,26 +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. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../../..', - roots: ['/x-pack/plugins/ess_security/public/common'], - testMatch: ['/x-pack/plugins/ess_security/public/common/**/*.test.{js,mjs,ts,tsx}'], - coverageDirectory: - '/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/common', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/x-pack/plugins/ess_security/public/common/**/*.{ts,tsx}', - '!/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}', - '!/x-pack/plugins/ess_security/public/common/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', - '!/x-pack/plugins/ess_security/public/common/*mock*.{ts,tsx}', - '!/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}', - '!/x-pack/plugins/ess_security/public/common/*.d.ts', - '!/x-pack/plugins/ess_security/public/common/*.config.ts', - '!/x-pack/plugins/ess_security/public/common/index.{js,ts,tsx}', - ], -}; diff --git a/x-pack/plugins/ess_security/public/jest.config.js b/x-pack/plugins/ess_security/public/jest.config.js index 2bdbadf77ba18..ffee6062ec59b 100644 --- a/x-pack/plugins/ess_security/public/jest.config.js +++ b/x-pack/plugins/ess_security/public/jest.config.js @@ -8,7 +8,7 @@ module.exports = { preset: '@kbn/test', rootDir: '../../../..', /** all nested directories have their own Jest config file */ - testMatch: ['/x-pack/plugins/ess_security/public/*.test.{js,mjs,ts,tsx}'], + testMatch: ['/x-pack/plugins/ess_security/public/**/*.test.{js,mjs,ts,tsx}'], roots: ['/x-pack/plugins/ess_security/public'], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/ess_security/public', coverageReporters: ['text', 'html'], diff --git a/x-pack/plugins/ess_security/public/plugin.ts b/x-pack/plugins/ess_security/public/plugin.ts index a87744e00ffcd..52d75c01f8119 100644 --- a/x-pack/plugins/ess_security/public/plugin.ts +++ b/x-pack/plugins/ess_security/public/plugin.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { subscribeBreadcrumbs } from './breadcrumbs'; import { getSecurityGetStartedComponent } from './get_started'; import { EssSecurityPluginSetup, @@ -26,8 +27,8 @@ export class EssSecurityPlugin constructor() {} public setup( - core: CoreSetup, - setupDeps: EssSecurityPluginSetupDependencies + _core: CoreSetup, + _setupDeps: EssSecurityPluginSetupDependencies ): EssSecurityPluginSetup { return {}; } @@ -37,6 +38,8 @@ export class EssSecurityPlugin startDeps: EssSecurityPluginStartDependencies ): EssSecurityPluginStart { const { securitySolution } = startDeps; + + subscribeBreadcrumbs(securitySolution, core); securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps)); return {}; diff --git a/x-pack/plugins/fleet/common/constants/file_storage.ts b/x-pack/plugins/fleet/common/constants/file_storage.ts index 554176f40e263..0d796d691b97b 100644 --- a/x-pack/plugins/fleet/common/constants/file_storage.ts +++ b/x-pack/plugins/fleet/common/constants/file_storage.ts @@ -8,26 +8,26 @@ // File storage indexes supporting file upload from the host to Elastic/Kibana // If needing to get an integration specific index name, use the utility functions // found in `common/services/file_storage` -export const FILE_STORAGE_METADATA_INDEX_PATTERN = '.fleet-files-*'; -export const FILE_STORAGE_DATA_INDEX_PATTERN = '.fleet-file-data-*'; +export const FILE_STORAGE_METADATA_INDEX_PATTERN = '.fleet-fileds-fromhost-meta-*'; +export const FILE_STORAGE_DATA_INDEX_PATTERN = '.fleet-fileds-fromhost-data-*'; -// File storage indexes supporting user uplaoded files (via kibana) that will be +// File storage indexes supporting user uploaded files (via kibana) that will be // delivered to the host agent/endpoint -export const FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN = '.fleet-filedelivery-meta-*'; -export const FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN = '.fleet-filedelivery-data-*'; +export const FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN = '.fleet-fileds-tohost-meta-*'; +export const FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN = '.fleet-fileds-tohost-data-*'; // which integrations support file upload and the name to use for the file upload index export const FILE_STORAGE_INTEGRATION_INDEX_NAMES: Readonly< Record< string, - { + Readonly<{ /** name to be used for the index */ name: string; /** If integration supports files sent from host to ES/Kibana */ fromHost: boolean; /** If integration supports files to be sent to host from kibana */ toHost: boolean; - } + }> > > = { elastic_agent: { name: 'agent', fromHost: true, toHost: false }, diff --git a/x-pack/plugins/fleet/common/services/file_storage.test.ts b/x-pack/plugins/fleet/common/services/file_storage.test.ts index 0360d7311eb6a..dbf5da61dba1d 100644 --- a/x-pack/plugins/fleet/common/services/file_storage.test.ts +++ b/x-pack/plugins/fleet/common/services/file_storage.test.ts @@ -5,24 +5,41 @@ * 2.0. */ +import { FILE_STORAGE_METADATA_INDEX_PATTERN } from '../constants'; + import { getFileDataIndexName, getFileMetadataIndexName } from '..'; +import { getIntegrationNameFromIndexName } from './file_storage'; + describe('File Storage services', () => { describe('File Index Names', () => { it('should generate file metadata index name for files received from host', () => { - expect(getFileMetadataIndexName('foo')).toEqual('.fleet-files-foo'); + expect(getFileMetadataIndexName('foo')).toEqual('.fleet-fileds-fromhost-meta-foo'); }); it('should generate file data index name for files received from host', () => { - expect(getFileDataIndexName('foo')).toEqual('.fleet-file-data-foo'); + expect(getFileDataIndexName('foo')).toEqual('.fleet-fileds-fromhost-data-foo'); }); it('should generate file metadata index name for files to be delivered to host', () => { - expect(getFileMetadataIndexName('foo', true)).toEqual('.fleet-filedelivery-meta-foo'); + expect(getFileMetadataIndexName('foo', true)).toEqual('.fleet-fileds-tohost-meta-foo'); }); it('should generate file data index name for files to be delivered to host', () => { - expect(getFileDataIndexName('foo', true)).toEqual('.fleet-filedelivery-data-foo'); + expect(getFileDataIndexName('foo', true)).toEqual('.fleet-fileds-tohost-data-foo'); + }); + }); + + describe('getIntegrationNameFromIndexName()', () => { + it.each([ + ['regular index names', '.fleet-fileds-fromhost-meta-agent'], + ['datastream index names', '.ds-.fleet-fileds-fromhost-data-agent-2023.06.30-00001'], + ])('should handle %s', (_, index) => { + expect(getIntegrationNameFromIndexName(index, FILE_STORAGE_METADATA_INDEX_PATTERN)).toEqual( + 'agent' + ); }); + + it.todo('should error if index pattern does not include `*`'); }); }); diff --git a/x-pack/plugins/fleet/common/services/file_storage.ts b/x-pack/plugins/fleet/common/services/file_storage.ts index 6581f671df663..af909a22aa946 100644 --- a/x-pack/plugins/fleet/common/services/file_storage.ts +++ b/x-pack/plugins/fleet/common/services/file_storage.ts @@ -56,21 +56,19 @@ export const getFileDataIndexName = ( ); }; -/** - * Returns the write index name for a given file upload alias name, this is the same for metadata and chunks - * @param aliasName - */ -export const getFileWriteIndexName = (aliasName: string) => aliasName + '-000001'; /** * Returns back the integration name for a given File Data (chunks) index name. * * @example - * // Given a File data index pattern of `.fleet-file-data-*`: + * // Given a File data index pattern of `.fleet-fileds-fromhost-data-*`: * - * getIntegrationNameFromFileDataIndexName('.fleet-file-data-agent'); + * getIntegrationNameFromFileDataIndexName('.fleet-fileds-fromhost-data-agent'); * // return 'agent' * - * getIntegrationNameFromFileDataIndexName('.fleet-file-data-agent-00001'); + * getIntegrationNameFromFileDataIndexName('.ds-.fleet-fileds-fromhost-data-agent'); + * // return 'agent' + * + * getIntegrationNameFromFileDataIndexName('.ds-.fleet-fileds-fromhost-data-agent-2023.06.30-00001'); * // return 'agent' */ export const getIntegrationNameFromFileDataIndexName = (indexName: string): string => { @@ -87,7 +85,7 @@ export const getIntegrationNameFromIndexName = ( throw new Error(`Unable to parse index name. No '*' in index pattern: ${indexPattern}`); } - const indexPieces = indexName.split('-'); + const indexPieces = indexName.replace(/^\.ds-/, '').split('-'); if (indexPieces[integrationNameIndexPosition]) { return indexPieces[integrationNameIndexPosition]; @@ -95,15 +93,3 @@ export const getIntegrationNameFromIndexName = ( throw new Error(`Index name ${indexName} does not seem to be a File storage index`); }; - -export const getFileStorageWriteIndexBody = (aliasName: string) => ({ - aliases: { - [aliasName]: { - is_write_index: true, - }, - }, - settings: { - 'index.lifecycle.rollover_alias': aliasName, - 'index.hidden': true, - }, -}); diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index f86c939456445..f0b01080db22d 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -40,7 +40,7 @@ "kibanaReact", "cloudChat", "esUiShared", - "infra", + "logsShared", "kibanaUtils", "usageCollection", "unifiedSearch" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index 4af6b30c99fb4..e6dae29cea881 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -30,7 +30,7 @@ import semverCoerce from 'semver/functions/coerce'; import { createStateContainerReactHelpers } from '@kbn/kibana-utils-plugin/public'; import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public'; import type { TimeRange } from '@kbn/es-query'; -import { LogStream, type LogStreamProps } from '@kbn/infra-plugin/public'; +import { LogStream, type LogStreamProps } from '@kbn/logs-shared-plugin/public'; import type { Agent, AgentPolicy } from '../../../../../types'; import { useLink, useStartServices } from '../../../../../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx index b9a023bef1ef7..2f6e20f097601 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx @@ -46,7 +46,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { const { spaces } = useStartServices(); const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets'); - const canReadPackageSettings = useAuthz().integrations.readPackageSettings; + const canReadPackageSettings = useAuthz().integrations.readPackageInfo; const { getPath } = useLink(); const getPackageInstallStatus = useGetPackageInstallStatus(); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index f5ef34d28b5c4..1780d3e55169a 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -11,18 +11,9 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { IndicesCreateRequest } from '@elastic/elasticsearch/lib/api/types'; -import { - FILE_STORAGE_INTEGRATION_INDEX_NAMES, - FILE_STORAGE_INTEGRATION_NAMES, -} from '../../../../../common/constants'; - import { ElasticsearchAssetType } from '../../../../types'; import { - getFileWriteIndexName, - getFileStorageWriteIndexBody, getPipelineNameForDatastream, - getFileDataIndexName, - getFileMetadataIndexName, getRegistryDataStreamAssetBaseName, } from '../../../../../common/services'; import type { @@ -440,63 +431,6 @@ export async function ensureDefaultComponentTemplates( ); } -/* - * Given a list of integration names, if the integrations support file upload - * then ensure that the alias has a matching write index, as we use "plain" indices - * not data streams. - * e.g .fleet-file-data-agent must have .fleet-file-data-agent-00001 as the write index - * before files can be uploaded. - */ -export async function ensureFileUploadWriteIndices(opts: { - esClient: ElasticsearchClient; - logger: Logger; - integrationNames: string[]; -}) { - const { esClient, logger, integrationNames } = opts; - - const integrationsWithFileUpload = integrationNames.filter((integration) => - FILE_STORAGE_INTEGRATION_NAMES.includes(integration as any) - ); - - if (!integrationsWithFileUpload.length) return []; - - const ensure = (aliasName: string) => - ensureAliasHasWriteIndex({ - esClient, - logger, - aliasName, - writeIndexName: getFileWriteIndexName(aliasName), - body: getFileStorageWriteIndexBody(aliasName), - }); - - return Promise.all( - integrationsWithFileUpload.flatMap((integrationName) => { - const { - name: indexName, - fromHost, - toHost, - } = FILE_STORAGE_INTEGRATION_INDEX_NAMES[integrationName]; - const indexCreateRequests: Array> = []; - - if (fromHost) { - indexCreateRequests.push( - ensure(getFileDataIndexName(indexName)), - ensure(getFileMetadataIndexName(indexName)) - ); - } - - if (toHost) { - indexCreateRequests.push( - ensure(getFileDataIndexName(indexName, true)), - ensure(getFileMetadataIndexName(indexName, true)) - ); - } - - return indexCreateRequests; - }) - ); -} - export async function ensureComponentTemplate( esClient: ElasticsearchClient, logger: Logger, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index b884e8c893de8..a5b4ad6f4e00b 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -37,7 +37,6 @@ import type { PackageVerificationResult, IndexTemplateEntry, } from '../../../types'; -import { ensureFileUploadWriteIndices } from '../elasticsearch/template/install'; import { removeLegacyTemplates } from '../elasticsearch/template/remove_legacy'; import { isTopLevelPipeline, deletePreviousPipelines } from '../elasticsearch/ingest_pipeline'; import { installILMPolicy } from '../elasticsearch/ilm/install'; @@ -236,15 +235,6 @@ export async function _installPackage({ logger.warn(`Error removing legacy templates: ${e.message}`); } - const { diagnosticFileUploadEnabled } = appContextService.getExperimentalFeatures(); - if (diagnosticFileUploadEnabled) { - await ensureFileUploadWriteIndices({ - integrationNames: [packageInfo.name], - esClient, - logger, - }); - } - // update current backing indices of each data stream await withPackageSpan('Update write indices', () => updateCurrentWriteIndices(esClient, logger, indexTemplates) diff --git a/x-pack/plugins/fleet/server/services/files/client_from_host.test.ts b/x-pack/plugins/fleet/server/services/files/client_from_host.test.ts index 068bad018b5e8..0051418b1c00d 100644 --- a/x-pack/plugins/fleet/server/services/files/client_from_host.test.ts +++ b/x-pack/plugins/fleet/server/services/files/client_from_host.test.ts @@ -91,11 +91,11 @@ describe('FleetFromHostFilesClient', () => { esClientMock.search.mockImplementation(async (searchRequest = {}) => { // File metadata - if ((searchRequest.index as string).startsWith('.fleet-files-')) { + if ((searchRequest.index as string).startsWith('.fleet-fileds-fromhost-meta-')) { return fleetFilesIndexSearchResponse; } - if ((searchRequest.index as string).startsWith('.fleet-file-data-')) { + if ((searchRequest.index as string).startsWith('.fleet-fileds-fromhost-data-')) { return fleetFileDataIndexSearchResponse; } @@ -111,8 +111,8 @@ describe('FleetFromHostFilesClient', () => { expect(createEsFileClientMock).toHaveBeenCalledWith({ elasticsearchClient: esClientMock, logger: loggerMock, - metadataIndex: '.fleet-files-foo', - blobStorageIndex: '.fleet-file-data-foo', + metadataIndex: '.fleet-fileds-fromhost-meta-foo', + blobStorageIndex: '.fleet-fileds-fromhost-data-foo', indexIsAlias: true, }); }); @@ -159,7 +159,7 @@ describe('FleetFromHostFilesClient', () => { }, }, }, - index: '.fleet-file-data-foo', + index: '.fleet-fileds-fromhost-data-foo', size: 0, }); }); diff --git a/x-pack/plugins/fleet/server/services/files/client_to_host.test.ts b/x-pack/plugins/fleet/server/services/files/client_to_host.test.ts index a4820f256da95..1068f34366970 100644 --- a/x-pack/plugins/fleet/server/services/files/client_to_host.test.ts +++ b/x-pack/plugins/fleet/server/services/files/client_to_host.test.ts @@ -130,8 +130,8 @@ describe('FleetToHostFilesClient', () => { expect(createEsFileClientMock).toHaveBeenCalledWith({ elasticsearchClient: esClientMock, logger: loggerMock, - metadataIndex: '.fleet-filedelivery-meta-foo', - blobStorageIndex: '.fleet-filedelivery-data-foo', + metadataIndex: '.fleet-fileds-tohost-meta-foo', + blobStorageIndex: '.fleet-fileds-tohost-data-foo', maxSizeBytes: 12345, indexIsAlias: true, }); diff --git a/x-pack/plugins/fleet/server/services/files/index.ts b/x-pack/plugins/fleet/server/services/files/index.ts index 5790164b81d07..8d6cbdb9fd5a4 100644 --- a/x-pack/plugins/fleet/server/services/files/index.ts +++ b/x-pack/plugins/fleet/server/services/files/index.ts @@ -34,22 +34,27 @@ export async function getFilesByStatus( abortController: AbortController, status: FileStatus = 'READY' ): Promise { - const result = await esClient.search( - { - index: FILE_STORAGE_METADATA_INDEX_PATTERN, - body: { - size: ES_SEARCH_LIMIT, - query: { - term: { - 'file.Status': status, + const result = await esClient + .search( + { + index: FILE_STORAGE_METADATA_INDEX_PATTERN, + body: { + size: ES_SEARCH_LIMIT, + query: { + term: { + 'file.Status': status, + }, }, + _source: false, }, - _source: false, + ignore_unavailable: true, }, - ignore_unavailable: true, - }, - { signal: abortController.signal } - ); + { signal: abortController.signal } + ) + .catch((err) => { + Error.captureStackTrace(err); + throw err; + }); return result.hits.hits; } @@ -84,32 +89,37 @@ export async function fileIdsWithoutChunksByIndex( return acc; }, {} as FileIdsByIndex); - const chunks = await esClient.search<{ bid: string }>( - { - index: FILE_STORAGE_DATA_INDEX_PATTERN, - body: { - size: ES_SEARCH_LIMIT, - query: { - bool: { - must: [ - { - terms: { - bid: Array.from(allFileIds), + const chunks = await esClient + .search<{ bid: string }>( + { + index: FILE_STORAGE_DATA_INDEX_PATTERN, + body: { + size: ES_SEARCH_LIMIT, + query: { + bool: { + must: [ + { + terms: { + bid: Array.from(allFileIds), + }, }, - }, - { - term: { - last: true, + { + term: { + last: true, + }, }, - }, - ], + ], + }, }, + _source: ['bid'], }, - _source: ['bid'], }, - }, - { signal: abortController.signal } - ); + { signal: abortController.signal } + ) + .catch((err) => { + Error.captureStackTrace(err); + throw err; + }); chunks.hits.hits.forEach((hit) => { const fileId = hit._source?.bid; @@ -140,22 +150,27 @@ export function updateFilesStatus( ): Promise { return Promise.all( Object.entries(fileIdsByIndex).map(([index, fileIds]) => { - return esClient.updateByQuery( - { - index, - refresh: true, - query: { - ids: { - values: Array.from(fileIds), + return esClient + .updateByQuery( + { + index, + refresh: true, + query: { + ids: { + values: Array.from(fileIds), + }, + }, + script: { + source: `ctx._source.file.Status = '${status}'`, + lang: 'painless', }, }, - script: { - source: `ctx._source.file.Status = '${status}'`, - lang: 'painless', - }, - }, - { signal: abortController.signal } - ); + { signal: abortController.signal } + ) + .catch((err) => { + Error.captureStackTrace(err); + throw err; + }); }) ); } diff --git a/x-pack/plugins/fleet/server/services/files/mocks.ts b/x-pack/plugins/fleet/server/services/files/mocks.ts index 35c276bf5cae7..23c0482b7e111 100644 --- a/x-pack/plugins/fleet/server/services/files/mocks.ts +++ b/x-pack/plugins/fleet/server/services/files/mocks.ts @@ -86,7 +86,7 @@ export const createFromHostEsSearchResponseMock = max_score: 0, hits: [ { - _index: '.fleet-files-foo-000001', + _index: '.fleet-fileds-fromhost-meta-foo-000001', _id: '123', _score: 1.0, _source: { diff --git a/x-pack/plugins/fleet/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts index 7d98db879910b..15dccb15053ab 100644 --- a/x-pack/plugins/fleet/server/services/setup.test.ts +++ b/x-pack/plugins/fleet/server/services/setup.test.ts @@ -15,9 +15,7 @@ import { ensurePreconfiguredPackagesAndPolicies } from '.'; import { appContextService } from './app_context'; import { getInstallations } from './epm/packages'; import { upgradeManagedPackagePolicies } from './managed_package_policies'; -import { setupFleet, ensureFleetFileUploadIndices } from './setup'; - -import { ensureFileUploadWriteIndices } from './epm/elasticsearch/template/install'; +import { setupFleet } from './setup'; jest.mock('./preconfiguration'); jest.mock('./preconfiguration/outputs'); @@ -70,8 +68,6 @@ describe('setupFleet', () => { soClient.find.mockResolvedValue({ saved_objects: [] } as any); soClient.bulkGet.mockResolvedValue({ saved_objects: [] } as any); - - (ensureFileUploadWriteIndices as jest.Mock).mockResolvedValue({}); }); afterEach(async () => { @@ -138,12 +134,4 @@ describe('setupFleet', () => { ], }); }); - - it('should create agent file upload write indices', async () => { - await ensureFleetFileUploadIndices(soClient, esClient); - - expect((ensureFileUploadWriteIndices as jest.Mock).mock.calls[0][0].integrationNames).toEqual([ - 'elastic_agent', - ]); - }); }); diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 90b76b8495e39..92d8f8fb37dda 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -12,11 +12,7 @@ import pMap from 'p-map'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import { - AUTO_UPDATE_PACKAGES, - FILE_STORAGE_INTEGRATION_NAMES, - FLEET_ELASTIC_AGENT_PACKAGE, -} from '../../common/constants'; +import { AUTO_UPDATE_PACKAGES } from '../../common/constants'; import type { PreconfigurationError } from '../../common/constants'; import type { DefaultPackagesInstallationError, @@ -44,10 +40,7 @@ import { ensureDefaultEnrollmentAPIKeyForAgentPolicy } from './api_keys'; import { getRegistryUrl, settingsService } from '.'; import { awaitIfPending } from './setup_utils'; import { ensureFleetFinalPipelineIsInstalled } from './epm/elasticsearch/ingest_pipeline/install'; -import { - ensureDefaultComponentTemplates, - ensureFileUploadWriteIndices, -} from './epm/elasticsearch/template/install'; +import { ensureDefaultComponentTemplates } from './epm/elasticsearch/template/install'; import { getInstallations, reinstallPackageForInstallation } from './epm/packages'; import { isPackageInstalled } from './epm/packages/install'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; @@ -60,7 +53,6 @@ import { ensurePreconfiguredFleetServerHosts, getPreconfiguredFleetServerHostFromConfig, } from './preconfiguration/fleet_server_host'; -import { getInstallationsByName } from './epm/packages/get'; export interface SetupStatus { isInitialized: boolean; @@ -125,7 +117,6 @@ async function createSetupSideEffects( logger.debug('Setting up Fleet Elasticsearch assets'); await ensureFleetGlobalEsAssets(soClient, esClient); - await ensureFleetFileUploadIndices(soClient, esClient); // Ensure that required packages are always installed even if they're left out of the config const preconfiguredPackageNames = new Set(packages.map((pkg) => pkg.name)); @@ -207,32 +198,6 @@ async function createSetupSideEffects( }; } -/** - * Ensure ES assets shared by all Fleet index template are installed - */ -export async function ensureFleetFileUploadIndices( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient -) { - const { diagnosticFileUploadEnabled } = appContextService.getExperimentalFeatures(); - if (!diagnosticFileUploadEnabled) return; - const logger = appContextService.getLogger(); - const installedFileUploadIntegrations = await getInstallationsByName({ - savedObjectsClient: soClient, - pkgNames: [...FILE_STORAGE_INTEGRATION_NAMES], - }); - - const integrationNames = installedFileUploadIntegrations.map(({ name }) => name); - if (!integrationNames.includes(FLEET_ELASTIC_AGENT_PACKAGE)) { - integrationNames.push(FLEET_ELASTIC_AGENT_PACKAGE); - } - logger.debug(`Ensuring file upload write indices for ${integrationNames}`); - return ensureFileUploadWriteIndices({ - esClient, - logger, - integrationNames, - }); -} /** * Ensure ES assets shared by all Fleet index template are installed */ diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts index b7ebdd0748e9d..a7611d73cd313 100644 --- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts +++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts @@ -72,6 +72,7 @@ export class CheckDeletedFilesTask { } this.wasStarted = true; + this.logger.info(`Started with interval of [${INTERVAL}] and timeout of [${TIMEOUT}]`); try { await taskManager.ensureScheduled({ @@ -85,7 +86,7 @@ export class CheckDeletedFilesTask { params: { version: VERSION }, }); } catch (e) { - this.logger.error(`Error scheduling task, received error: ${e}`); + this.logger.error(`Error scheduling task, received error: ${e.message}`, e); } }; @@ -104,19 +105,34 @@ export class CheckDeletedFilesTask { throwUnrecoverableError(new Error('Outdated task version')); } + this.logger.info(`[runTask()] started`); + + const endRun = (msg: string = '') => { + this.logger.info(`[runTask()] ended${msg ? ': ' + msg : ''}`); + }; + const [{ elasticsearch }] = await core.getStartServices(); const esClient = elasticsearch.client.asInternalUser; try { const readyFiles = await getFilesByStatus(esClient, this.abortController); - if (!readyFiles.length) return; + + if (!readyFiles.length) { + endRun('no files to process'); + return; + } const { fileIdsByIndex: deletedFileIdsByIndex, allFileIds: allDeletedFileIds } = await fileIdsWithoutChunksByIndex(esClient, this.abortController, readyFiles); - if (!allDeletedFileIds.size) return; + + if (!allDeletedFileIds.size) { + endRun('No files with deleted chunks'); + return; + } this.logger.info(`Attempting to update ${allDeletedFileIds.size} files to DELETED status`); - this.logger.debug(`Attempting to file ids: ${deletedFileIdsByIndex}`); + this.logger.debug(`Attempting to update file ids: ${deletedFileIdsByIndex}`); + const updatedFilesResponses = await updateFilesStatus( esClient, this.abortController, @@ -130,12 +146,16 @@ export class CheckDeletedFilesTask { this.logger.warn(`Failed to update ${failures.length} files to DELETED status`); this.logger.debug(`Failed to update files to DELETED status: ${failures}`); } + + endRun('success'); } catch (err) { if (err instanceof errors.RequestAbortedError) { this.logger.warn(`request aborted due to timeout: ${err}`); + endRun(); return; } this.logger.error(err); + endRun('error'); } }; } diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index dc858ff5029a8..4e8ec7de3fcc7 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -42,7 +42,7 @@ "@kbn/cloud-chat-plugin", "@kbn/kibana-react-plugin", "@kbn/es-ui-shared-plugin", - "@kbn/infra-plugin", + "@kbn/logs-shared-plugin", "@kbn/kibana-utils-plugin", "@kbn/unified-search-plugin", "@kbn/storybook", diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts index 2eec5b035c793..4f5b977dd3b82 100644 --- a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts +++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import * as rt from 'io-ts'; -import { persistedLogViewReferenceRT } from '../../../log_views'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; export const LOG_DOCUMENT_COUNT_RULE_TYPE_ID = 'logs.alert.document.count'; diff --git a/x-pack/plugins/infra/common/http_api/index.ts b/x-pack/plugins/infra/common/http_api/index.ts index 9f63896dbca9f..cfa4841d9aa57 100644 --- a/x-pack/plugins/infra/common/http_api/index.ts +++ b/x-pack/plugins/infra/common/http_api/index.ts @@ -20,6 +20,4 @@ export * as inventoryViewsV1 from './inventory_views/v1'; export * as logAlertsV1 from './log_alerts/v1'; export * as logAnalysisResultsV1 from './log_analysis/results/v1'; export * as logAnalysisValidationV1 from './log_analysis/validation/v1'; -export * as logEntriesV1 from './log_entries/v1'; -export * as logViewsV1 from './log_views/v1'; export * as metricsExplorerViewsV1 from './metrics_explorer_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts index 65e056f30e0d9..f1eb9b24ee039 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; + import * as rt from 'io-ts'; import { either } from 'fp-ts/Either'; +import { inventoryViewRT } from '../../../inventory_views'; export const INVENTORY_VIEW_URL = '/api/infra/inventory_views'; export const INVENTORY_VIEW_URL_ENTITY = `${INVENTORY_VIEW_URL}/{inventoryViewId}`; @@ -33,30 +34,8 @@ export const inventoryViewRequestQueryRT = rt.partial({ sourceId: rt.string, }); -export type InventoryViewRequestQuery = rt.TypeOf; - -const inventoryViewAttributesResponseRT = rt.intersection([ - rt.strict({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, - }), - rt.UnknownRecord, -]); - -const inventoryViewResponseRT = rt.exact( - rt.intersection([ - rt.type({ - id: rt.string, - attributes: inventoryViewAttributesResponseRT, - }), - rt.partial({ - updatedAt: rt.number, - version: rt.string, - }), - ]) -); - export const inventoryViewResponsePayloadRT = rt.type({ - data: inventoryViewResponseRT, + data: inventoryViewRT, }); + +export type InventoryViewRequestQuery = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts index 8bad088b00542..67a3bd7df1a70 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts @@ -5,16 +5,18 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { inventoryViewAttributesRT, inventoryViewRT } from '../../../inventory_views'; -export const createInventoryViewAttributesRequestPayloadRT = rt.intersection([ - rt.type({ - name: nonEmptyStringRt, - }), - rt.UnknownRecord, - rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), -]); +export const createInventoryViewAttributesRequestPayloadRT = rt.exact( + rt.intersection([ + inventoryViewAttributesRT, + rt.partial({ + isDefault: rt.undefined, + isStatic: rt.undefined, + }), + ]) +); export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf< typeof createInventoryViewAttributesRequestPayloadRT @@ -23,3 +25,5 @@ export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf< export const createInventoryViewRequestPayloadRT = rt.type({ attributes: createInventoryViewAttributesRequestPayloadRT, }); + +export type CreateInventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts index 17a9820a93a4d..3452a22a45d17 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts @@ -5,28 +5,11 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; - -export const findInventoryViewAttributesResponseRT = rt.strict({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, -}); - -const findInventoryViewResponseRT = rt.exact( - rt.intersection([ - rt.type({ - id: rt.string, - attributes: findInventoryViewAttributesResponseRT, - }), - rt.partial({ - updatedAt: rt.number, - version: rt.string, - }), - ]) -); +import { singleInventoryViewRT } from '../../../inventory_views'; export const findInventoryViewResponsePayloadRT = rt.type({ - data: rt.array(findInventoryViewResponseRT), + data: rt.array(singleInventoryViewRT), }); + +export type FindInventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts index 8e5cef06bb916..a13541c1e8a44 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts @@ -6,7 +6,10 @@ */ import * as rt from 'io-ts'; +import { inventoryViewRT } from '../../../inventory_views'; export const getInventoryViewRequestParamsRT = rt.type({ inventoryViewId: rt.string, }); + +export type GetInventoryViewResposePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts index b21bafbecec18..5698ab2a0b2c9 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts @@ -5,16 +5,18 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { inventoryViewAttributesRT, inventoryViewRT } from '../../../inventory_views'; -export const updateInventoryViewAttributesRequestPayloadRT = rt.intersection([ - rt.type({ - name: nonEmptyStringRt, - }), - rt.UnknownRecord, - rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), -]); +export const updateInventoryViewAttributesRequestPayloadRT = rt.exact( + rt.intersection([ + inventoryViewAttributesRT, + rt.partial({ + isDefault: rt.undefined, + isStatic: rt.undefined, + }), + ]) +); export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf< typeof updateInventoryViewAttributesRequestPayloadRT @@ -23,3 +25,5 @@ export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf< export const updateInventoryViewRequestPayloadRT = rt.type({ attributes: updateInventoryViewAttributesRequestPayloadRT, }); + +export type UpdateInventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/latest.ts b/x-pack/plugins/infra/common/http_api/latest.ts index 28cdf220f710e..98787a2c581a2 100644 --- a/x-pack/plugins/infra/common/http_api/latest.ts +++ b/x-pack/plugins/infra/common/http_api/latest.ts @@ -9,6 +9,4 @@ export * from './inventory_views/v1'; export * from './log_alerts/v1'; export * from './log_analysis/results/v1'; export * from './log_analysis/validation/v1'; -export * from './log_entries/v1'; -export * from './log_views/v1'; export * from './metrics_explorer_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/log_alerts/v1/chart_preview_data.ts b/x-pack/plugins/infra/common/http_api/log_alerts/v1/chart_preview_data.ts index 5647b9d09bdcc..7f0424f0df53b 100644 --- a/x-pack/plugins/infra/common/http_api/log_alerts/v1/chart_preview_data.ts +++ b/x-pack/plugins/infra/common/http_api/log_alerts/v1/chart_preview_data.ts @@ -6,6 +6,7 @@ */ import * as rt from 'io-ts'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { ThresholdRT, countCriteriaRT, @@ -13,7 +14,6 @@ import { timeSizeRT, groupByRT, } from '../../../alerting/logs/log_threshold/types'; -import { persistedLogViewReferenceRT } from '../../../log_views'; export const LOG_ALERTS_CHART_PREVIEW_DATA_PATH = '/api/infra/log_alerts/chart_preview_data'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies.ts index 56ee97a468462..3553962063990 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -import { persistedLogViewReferenceRT } from '../../../../log_views'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { timeRangeRT, routeTimingMetadataRT } from '../../../shared'; import { logEntryAnomalyRT, diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies_datasets.ts index 8de55d96ba3c0..c07007be05115 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_anomalies_datasets.ts @@ -6,7 +6,7 @@ */ import * as rt from 'io-ts'; -import { persistedLogViewReferenceRT } from '../../../../log_views'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { badRequestErrorRT, diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_categories.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_categories.ts index 9e838174bb6a1..e84825b8c6835 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_categories.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_categories.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -import { persistedLogViewReferenceRT } from '../../../../log_views'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { badRequestErrorRT, forbiddenErrorRT, diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_datasets.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_datasets.ts index 6523559103fdc..e051e313d9b8e 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_datasets.ts @@ -7,13 +7,13 @@ import * as rt from 'io-ts'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { badRequestErrorRT, forbiddenErrorRT, timeRangeRT, routeTimingMetadataRT, } from '../../../shared'; -import { persistedLogViewReferenceRT } from '../../../../log_views'; export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH = '/api/infra/log_analysis/results/log_entry_category_datasets'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_examples.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_examples.ts index 00fff8aabf9d1..fc6ece5d7b7f7 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_category_examples.ts @@ -5,16 +5,14 @@ * 2.0. */ +import { logEntryContextRT, persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import * as rt from 'io-ts'; - -import { persistedLogViewReferenceRT } from '../../../../log_views'; import { badRequestErrorRT, forbiddenErrorRT, - timeRangeRT, routeTimingMetadataRT, + timeRangeRT, } from '../../../shared'; -import { logEntryContextRT } from '../../../../log_entry'; export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH = '/api/infra/log_analysis/results/log_entry_category_examples'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_examples.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_examples.ts index 8233962df6bfa..ebc78693f4983 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_examples.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/v1/log_entry_examples.ts @@ -6,7 +6,7 @@ */ import * as rt from 'io-ts'; -import { persistedLogViewReferenceRT } from '../../../../log_views'; +import { persistedLogViewReferenceRT } from '@kbn/logs-shared-plugin/common'; import { logEntryExampleRT } from '../../../../log_analysis'; import { badRequestErrorRT, diff --git a/x-pack/plugins/infra/common/inventory_views/types.ts b/x-pack/plugins/infra/common/inventory_views/types.ts index 49979c1063efa..a493d2332f212 100644 --- a/x-pack/plugins/infra/common/inventory_views/types.ts +++ b/x-pack/plugins/infra/common/inventory_views/types.ts @@ -5,19 +5,89 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import { isoToEpochRt, nonEmptyStringRt, inRangeRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { + SnapshotCustomMetricInputRT, + SnapshotGroupByRT, + SnapshotMetricInputRT, +} from '../http_api/snapshot_api'; +import { ItemTypeRT } from '../inventory_models/types'; + +export const inventoryColorPaletteRT = rt.keyof({ + status: null, + temperature: null, + cool: null, + warm: null, + positive: null, + negative: null, +}); + +const inventoryLegendOptionsRT = rt.type({ + palette: inventoryColorPaletteRT, + steps: inRangeRt(2, 18), + reverseColors: rt.boolean, +}); + +export const inventorySortOptionRT = rt.type({ + by: rt.keyof({ name: null, value: null }), + direction: rt.keyof({ asc: null, desc: null }), +}); + +export const inventoryViewOptionsRT = rt.keyof({ table: null, map: null }); + +export const inventoryMapBoundsRT = rt.type({ + min: inRangeRt(0, 1), + max: inRangeRt(0, 1), +}); + +export const inventoryFiltersStateRT = rt.type({ + kind: rt.literal('kuery'), + expression: rt.string, +}); + +export const inventoryOptionsStateRT = rt.intersection([ + rt.type({ + accountId: rt.string, + autoBounds: rt.boolean, + boundsOverride: inventoryMapBoundsRT, + customMetrics: rt.array(SnapshotCustomMetricInputRT), + customOptions: rt.array( + rt.type({ + text: rt.string, + field: rt.string, + }) + ), + groupBy: SnapshotGroupByRT, + metric: SnapshotMetricInputRT, + nodeType: ItemTypeRT, + region: rt.string, + sort: inventorySortOptionRT, + view: inventoryViewOptionsRT, + }), + rt.partial({ legend: inventoryLegendOptionsRT, source: rt.string, timelineOpen: rt.boolean }), +]); + +export const inventoryViewBasicAttributesRT = rt.type({ + name: nonEmptyStringRt, +}); + +const inventoryViewFlagsRT = rt.partial({ isDefault: rt.boolean, isStatic: rt.boolean }); export const inventoryViewAttributesRT = rt.intersection([ - rt.strict({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, + inventoryOptionsStateRT, + inventoryViewBasicAttributesRT, + inventoryViewFlagsRT, + rt.type({ + autoReload: rt.boolean, + filterQuery: inventoryFiltersStateRT, }), - rt.UnknownRecord, + rt.partial({ time: rt.number }), ]); -export type InventoryViewAttributes = rt.TypeOf; +const singleInventoryViewAttributesRT = rt.exact( + rt.intersection([inventoryViewBasicAttributesRT, inventoryViewFlagsRT]) +); export const inventoryViewRT = rt.exact( rt.intersection([ @@ -26,10 +96,31 @@ export const inventoryViewRT = rt.exact( attributes: inventoryViewAttributesRT, }), rt.partial({ - updatedAt: rt.number, + updatedAt: isoToEpochRt, + version: rt.string, + }), + ]) +); + +export const singleInventoryViewRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + attributes: singleInventoryViewAttributesRT, + }), + rt.partial({ + updatedAt: isoToEpochRt, version: rt.string, }), ]) ); +export type InventoryColorPalette = rt.TypeOf; +export type InventoryFiltersState = rt.TypeOf; +export type InventoryLegendOptions = rt.TypeOf; +export type InventoryMapBounds = rt.TypeOf; +export type InventoryOptionsState = rt.TypeOf; +export type InventorySortOption = rt.TypeOf; export type InventoryView = rt.TypeOf; +export type InventoryViewAttributes = rt.TypeOf; +export type InventoryViewOptions = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/locators/helpers.ts b/x-pack/plugins/infra/common/locators/helpers.ts index 9ede09e7f1b1a..b4258053a0c01 100644 --- a/x-pack/plugins/infra/common/locators/helpers.ts +++ b/x-pack/plugins/infra/common/locators/helpers.ts @@ -5,23 +5,25 @@ * 2.0. */ -import { flowRight } from 'lodash'; import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import { findInventoryFields } from '../inventory_models'; -import { MESSAGE_FIELD, TIMESTAMP_FIELD } from '../constants'; -import type { TimeRange } from '../time'; -import type { LogsLocatorParams } from './logs_locator'; -import type { InfraClientCoreSetup } from '../../public/types'; import { DEFAULT_LOG_VIEW, LogViewColumnConfiguration, LogViewReference, + ResolvedLogView, +} from '@kbn/logs-shared-plugin/common'; +import { flowRight } from 'lodash'; +import type { InfraClientCoreSetup } from '../../public/types'; +import { MESSAGE_FIELD, TIMESTAMP_FIELD } from '../constants'; +import { findInventoryFields } from '../inventory_models'; +import type { TimeRange } from '../time'; +import { replaceLogFilterInQueryString, replaceLogPositionInQueryString, replaceLogViewInQueryString, - ResolvedLogView, -} from '../log_views'; +} from '../url_state_storage_service'; +import type { LogsLocatorParams } from './logs_locator'; import type { NodeLogsLocatorParams } from './node_logs_locator'; interface LocationToDiscoverParams { @@ -59,9 +61,9 @@ export const getLocationToDiscover = async ({ filter, logView = DEFAULT_LOG_VIEW, }: LocationToDiscoverParams) => { - const [, plugins, pluginStart] = await core.getStartServices(); - const { discover } = plugins; - const { logViews } = pluginStart; + const [, plugins] = await core.getStartServices(); + const { discover, logsShared } = plugins; + const { logViews } = logsShared; const resolvedLogView = await logViews.client.getResolvedLogView(logView); const discoverParams: DiscoverAppLocatorParams = { diff --git a/x-pack/plugins/infra/common/locators/locators.test.ts b/x-pack/plugins/infra/common/locators/locators.test.ts index 5a4ea0047e2d7..c0573d942e757 100644 --- a/x-pack/plugins/infra/common/locators/locators.test.ts +++ b/x-pack/plugins/infra/common/locators/locators.test.ts @@ -13,7 +13,7 @@ import type { NodeLogsLocatorParams } from './node_logs_locator'; import { coreMock } from '@kbn/core/public/mocks'; import { findInventoryFields } from '../inventory_models'; import moment from 'moment'; -import { DEFAULT_LOG_VIEW, LogViewReference } from '../log_views'; +import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common'; const setupLogsLocator = async () => { const deps: LogsLocatorDependencies = { diff --git a/x-pack/plugins/infra/common/locators/logs_locator.ts b/x-pack/plugins/infra/common/locators/logs_locator.ts index f4e65634aa762..ee9006f359e66 100644 --- a/x-pack/plugins/infra/common/locators/logs_locator.ts +++ b/x-pack/plugins/infra/common/locators/logs_locator.ts @@ -7,7 +7,7 @@ import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import { SerializableRecord } from '@kbn/utility-types'; -import type { LogViewReference } from '../log_views'; +import type { LogViewReference } from '@kbn/logs-shared-plugin/common'; import type { TimeRange } from '../time'; import type { InfraClientCoreSetup } from '../../public/types'; diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/types.ts b/x-pack/plugins/infra/common/metrics_explorer_views/types.ts index 47ecb06ceace5..0d0c2fa3166e0 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/types.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/types.ts @@ -9,7 +9,7 @@ import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; export const metricsExplorerViewAttributesRT = rt.intersection([ - rt.strict({ + rt.type({ name: nonEmptyStringRt, isDefault: rt.boolean, isStatic: rt.boolean, diff --git a/x-pack/plugins/infra/common/saved_views/index.ts b/x-pack/plugins/infra/common/saved_views/index.ts new file mode 100644 index 0000000000000..6cc0ccaa93a6d --- /dev/null +++ b/x-pack/plugins/infra/common/saved_views/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './types'; diff --git a/x-pack/plugins/infra/common/saved_views/types.ts b/x-pack/plugins/infra/common/saved_views/types.ts new file mode 100644 index 0000000000000..01bf806da44d9 --- /dev/null +++ b/x-pack/plugins/infra/common/saved_views/types.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + QueryObserverBaseResult, + UseMutateAsyncFunction, + UseMutateFunction, +} from '@tanstack/react-query'; + +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; + +export type ServerError = IHttpFetchError; + +export interface SavedViewState { + views?: SavedViewItem[]; + currentView?: TView | null; + isCreatingView: boolean; + isFetchingCurrentView: boolean; + isFetchingViews: boolean; + isUpdatingView: boolean; +} + +export interface SavedViewOperations< + TView extends { id: TView['id'] }, + TId extends TView['id'] = TView['id'], + TPayload = any, + TConfig = any +> { + createView: UseMutateAsyncFunction; + deleteViewById: UseMutateFunction>; + fetchViews: QueryObserverBaseResult['refetch']; + updateViewById: UseMutateAsyncFunction>; + switchViewById: (id: TId) => void; + setDefaultViewById: UseMutateFunction>; +} + +export interface SavedViewResult< + TView extends { + id: TView['id']; + }, + TId extends string = '', + TPayload = any, + TConfig = any +> extends SavedViewState, + SavedViewOperations {} + +export interface UpdateViewParams { + id: string; + attributes: TRequestPayload; +} + +export interface MutationContext { + id?: string; + previousViews?: TView[]; +} + +export interface BasicAttributes { + name?: string; + time?: number; + isDefault?: boolean; + isStatic?: boolean; +} +export interface SavedViewItem { + id: string; + attributes: BasicAttributes; +} diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts index f8daaa1b9227b..c6b53abee7143 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts @@ -6,14 +6,15 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import * as rt from 'io-ts'; import { logEntryAfterCursorRT, logEntryBeforeCursorRT, logEntryCursorRT, logEntryRT, -} from '../../log_entry'; -import { logViewColumnConfigurationRT, logViewReferenceRT } from '../../log_views'; + logViewColumnConfigurationRT, + logViewReferenceRT, +} from '@kbn/logs-shared-plugin/common'; +import * as rt from 'io-ts'; import { jsonObjectRT } from '../../typed_json'; import { searchStrategyErrorRT } from '../common/errors'; diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts index 6d2a7891264d1..131450d588c0f 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts @@ -5,9 +5,12 @@ * 2.0. */ +import { + logEntryCursorRT, + logEntryFieldRT, + logViewReferenceRT, +} from '@kbn/logs-shared-plugin/common'; import * as rt from 'io-ts'; -import { logEntryCursorRT, logEntryFieldRT } from '../../log_entry'; -import { logViewReferenceRT } from '../../log_views'; import { searchStrategyErrorRT } from '../common/errors'; export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; diff --git a/x-pack/plugins/infra/common/log_views/url_state_storage_service.ts b/x-pack/plugins/infra/common/url_state_storage_service.ts similarity index 90% rename from x-pack/plugins/infra/common/log_views/url_state_storage_service.ts rename to x-pack/plugins/infra/common/url_state_storage_service.ts index 973fcd53ca98c..5d701ad1876bb 100644 --- a/x-pack/plugins/infra/common/log_views/url_state_storage_service.ts +++ b/x-pack/plugins/infra/common/url_state_storage_service.ts @@ -10,15 +10,15 @@ import { encode } from '@kbn/rison'; import type { Query } from '@kbn/es-query'; import { parse, stringify } from 'query-string'; import moment, { DurationInputObject } from 'moment'; -import type { FilterStateInUrl } from '../../public/observability_logs/log_stream_query_state'; -import type { PositionStateInUrl } from '../../public/observability_logs/log_stream_position_state/src/url_state_storage_service'; import { defaultFilterStateKey, defaultPositionStateKey, DEFAULT_REFRESH_INTERVAL, LogViewReference, -} from '.'; -import type { TimeRange } from '../time'; +} from '@kbn/logs-shared-plugin/common'; +import type { FilterStateInUrl } from '../public/observability_logs/log_stream_query_state'; +import type { PositionStateInUrl } from '../public/observability_logs/log_stream_position_state/src/url_state_storage_service'; +import type { TimeRange } from './time'; export const defaultLogViewKey = 'logView'; diff --git a/x-pack/plugins/infra/kibana.jsonc b/x-pack/plugins/infra/kibana.jsonc index c9a9ca0e18674..973d979ed90aa 100644 --- a/x-pack/plugins/infra/kibana.jsonc +++ b/x-pack/plugins/infra/kibana.jsonc @@ -20,6 +20,7 @@ "features", "fieldFormats", "lens", + "logsShared", "observability", "observabilityShared", "ruleRegistry", diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/explain_log_rate_spike.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/explain_log_rate_spike.tsx index b51c9beae3836..a8acacd8debd1 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/explain_log_rate_spike.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/explain_log_rate_spike.tsx @@ -52,7 +52,7 @@ export const ExplainLogRateSpikes: FC { const { services } = useKibanaContextForPlugin(); - const { dataViews, logViews } = services; + const { dataViews, logsShared } = services; const [dataView, setDataView] = useState(); const [esSearchQuery, setEsSearchQuery] = useState(); const [logSpikeParams, setLogSpikeParams] = useState< @@ -61,9 +61,8 @@ export const ExplainLogRateSpikes: FC { const getDataView = async () => { - const { timestampField, dataViewReference } = await logViews.client.getResolvedLogView( - rule.params.logView - ); + const { timestampField, dataViewReference } = + await logsShared.logViews.client.getResolvedLogView(rule.params.logView); if (dataViewReference.id) { const logDataView = await dataViews.get(dataViewReference.id); @@ -94,7 +93,7 @@ export const ExplainLogRateSpikes: FC import('./components/logs_history_chart')); @@ -44,7 +44,7 @@ const AlertDetailsAppSection = ({ alert, setAlertSummaryFields, }: AlertDetailsAppSectionProps) => { - const { observability, logViews } = useKibanaContextForPlugin().services; + const { observability, logsShared } = useKibanaContextForPlugin().services; const theme = useTheme(); const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; @@ -66,7 +66,7 @@ const AlertDetailsAppSection = ({ const { derivedDataView } = useLogView({ initialLogViewReference: rule.params.logView, - logViews: logViews.client, + logViews: logsShared.logViews.client, }); const { hasAtLeast } = useLicense(); diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx index df324169c98e0..bb2dbc7afdc36 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx @@ -9,7 +9,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, EuiContextMenuItem, EuiContextMenuPanel, EuiHeaderLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useLogViewContext } from '../../../hooks/use_log_view'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { AlertFlyout } from './alert_flyout'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx index c5af2938f5456..33f63fe6a49c3 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import type { PersistedLogViewReference, ResolvedLogViewField, -} from '../../../../../common/log_views'; +} from '@kbn/logs-shared-plugin/common'; import { Criterion } from './criterion'; import { PartialRuleParams, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx index 2e394bb093ec8..f2bb1d8ed5785 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx @@ -22,12 +22,12 @@ import { i18n } from '@kbn/i18n'; import { isFinite, isNumber } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; import type { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import type { ResolvedLogViewField } from '@kbn/logs-shared-plugin/common'; import { Comparator, ComparatorToi18nMap, Criterion as CriterionType, } from '../../../../../common/alerting/logs/log_threshold/types'; -import type { ResolvedLogViewField } from '../../../../../common/log_views'; const firstCriterionFieldPrefix = i18n.translate( 'xpack.infra.logs.alertFlyout.firstCriterionFieldPrefix', diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx index b8dbc56402b2b..6b2357a4f4611 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx @@ -21,9 +21,9 @@ import { } from '@elastic/charts'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getChartTheme } from '../../../../utils/get_chart_theme'; import { useIsDarkMode } from '../../../../hooks/use_is_dark_mode'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; import { ExecutionTimeRange } from '../../../../types'; import { ChartContainer, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index d9275a1ff0178..475bb0b1fbdef 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -13,7 +13,8 @@ import { ForLastExpression, RuleTypeParamsExpressionProps, } from '@kbn/triggers-actions-ui-plugin/public'; -import { PersistedLogViewReference, ResolvedLogViewField } from '../../../../../common/log_views'; +import { LogViewProvider, useLogViewContext } from '@kbn/logs-shared-plugin/public'; +import { PersistedLogViewReference, ResolvedLogViewField } from '@kbn/logs-shared-plugin/common'; import { Comparator, isOptimizableGroupedThreshold, @@ -28,7 +29,6 @@ import { import { decodeOrThrow } from '../../../../../common/runtime_types'; import { ObjectEntries } from '../../../../../common/utility_types'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; -import { LogViewProvider, useLogViewContext } from '../../../../hooks/use_log_view'; import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression'; import { errorsRT } from '../../validation'; import { Criteria } from './criteria'; @@ -95,7 +95,7 @@ export const ExpressionEditor: React.FC< > = (props) => { const isInternal = props.metadata?.isInternal ?? false; const { - services: { logViews }, + services: { logsShared }, } = useKibanaContextForPlugin(); // injected during alert registration return ( @@ -105,7 +105,7 @@ export const ExpressionEditor: React.FC< ) : ( - + diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx index d633a055c46ef..ad042d77a584c 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx @@ -8,6 +8,7 @@ import { HttpHandler } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useMemo, useState } from 'react'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { isRatioRule } from '../../../../../../common/alerting/logs/log_threshold'; import { GetLogAlertsChartPreviewDataAlertParamsSubset, @@ -16,7 +17,6 @@ import { getLogAlertsChartPreviewDataSuccessResponsePayloadRT, LOG_ALERTS_CHART_PREVIEW_DATA_PATH, } from '../../../../../../common/http_api'; -import { PersistedLogViewReference } from '../../../../../../common/log_views'; import { decodeOrThrow } from '../../../../../../common/runtime_types'; import { ExecutionTimeRange } from '../../../../../types'; import { useTrackedPromise } from '../../../../../utils/use_tracked_promise'; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/log_view_switcher.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/log_view_switcher.tsx index f3988002a7f7e..8dfa7295b6769 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/log_view_switcher.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/log_view_switcher.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem, EuiFlexGroup, EuiExpression, EuiToolTip } from '@elastic/eui'; -import { ResolvedLogView } from '../../../../../common/log_views'; +import { ResolvedLogView } from '@kbn/logs-shared-plugin/common'; const description = i18n.translate('xpack.infra.logs.alertFlyout.logViewDescription', { defaultMessage: 'Log View', diff --git a/x-pack/plugins/infra/public/apps/discover_app.tsx b/x-pack/plugins/infra/public/apps/discover_app.tsx index dd99a5e4dd625..2ea704fa9b21f 100644 --- a/x-pack/plugins/infra/public/apps/discover_app.tsx +++ b/x-pack/plugins/infra/public/apps/discover_app.tsx @@ -6,8 +6,8 @@ */ import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import type { AppMountParameters, CoreStart } from '@kbn/core/public'; +import { getLogViewReferenceFromUrl } from '@kbn/logs-shared-plugin/public'; import type { InfraClientStartExports } from '../types'; -import { getLogViewReferenceFromUrl } from '../observability_logs/log_view_state'; export const renderApp = ( core: CoreStart, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts index 20a0e1f2dc983..9599e65d4de9b 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts @@ -5,9 +5,19 @@ * 2.0. */ -import type { LensChartConfig } from '../../../types'; +import type { LensChartConfig, LensLineChartConfig } from '../../../types'; import { getFilters } from './utils'; +export const diskSpaceUsageLineChart: LensLineChartConfig = { + extraVisualizationState: { + yLeftExtent: { + mode: 'custom', + lowerBound: 0, + upperBound: 1, + }, + }, +}; + export const diskSpaceUsage: LensChartConfig = { title: 'Disk Space Usage', formula: { @@ -20,4 +30,5 @@ export const diskSpaceUsage: LensChartConfig = { }, }, getFilters, + lineChartConfig: diskSpaceUsageLineChart, }; diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/log_entries.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/log_entries.ts index 6212c256647ef..35549673701fc 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/log_entries.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/log_entries.ts @@ -11,11 +11,11 @@ import type { IKibanaSearchResponse, ISearchOptions, } from '@kbn/data-plugin/public'; +import { defaultLogViewAttributes } from '@kbn/logs-shared-plugin/common'; import { type LogEntriesSearchResponsePayload, LOG_ENTRIES_SEARCH_STRATEGY, } from '../../../../../../common/search_strategies/log_entries/log_entries'; -import { defaultLogViewAttributes } from '../../../../../../common/log_views'; import { generateFakeEntries } from '../../../../../test_utils/entries'; export const getLogEntries = ({ params }: IKibanaSearchRequest, options?: ISearchOptions) => { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx index d928eda5a2137..d2311b91fa1b2 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx @@ -11,10 +11,10 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; -import { DEFAULT_LOG_VIEW, LogViewReference } from '../../../../../common/log_views'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; +import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common'; import type { InventoryItemType } from '../../../../../common/inventory_models/types'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; -import { LogStream } from '../../../log_stream'; import { findInventoryFields } from '../../../../../common/inventory_models'; import { InfraLoadingPanel } from '../../../loading'; diff --git a/x-pack/plugins/infra/public/components/asset_details/types.ts b/x-pack/plugins/infra/public/components/asset_details/types.ts index c42953ab8fb3e..a3d519641da3a 100644 --- a/x-pack/plugins/infra/public/components/asset_details/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { LogViewReference } from '../../../common/log_views'; +import { LogViewReference } from '@kbn/logs-shared-plugin/common'; import type { InventoryItemType } from '../../../common/inventory_models/types'; import type { InfraAssetMetricType, SnapshotCustomMetricInput } from '../../../common/http_api'; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx index d2d59fe519353..1986354b5e9bd 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx @@ -13,10 +13,10 @@ import { Subscription } from 'rxjs'; import type { TimeRange } from '@kbn/es-query'; import { Embeddable, EmbeddableInput, IContainer } from '@kbn/embeddable-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import { CoreProviders } from '../../apps/common_providers'; import { InfraClientStartDeps, InfraClientStartExports } from '../../types'; import { datemathToEpochMillis } from '../../utils/datemath'; -import { LazyLogStreamWrapper } from './lazy_log_stream_wrapper'; export const LOG_STREAM_EMBEDDABLE = 'LOG_STREAM_EMBEDDABLE'; @@ -90,7 +90,7 @@ export class LogStreamEmbeddable extends Embeddable { >
- void; diff --git a/x-pack/plugins/infra/public/components/logging/log_search_controls/log_search_controls.tsx b/x-pack/plugins/infra/public/components/logging/log_search_controls/log_search_controls.tsx index e60a606ce0ce3..6533bebf74fc6 100644 --- a/x-pack/plugins/infra/public/components/logging/log_search_controls/log_search_controls.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_search_controls/log_search_controls.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import classNames from 'classnames'; import * as React from 'react'; -import { LogEntryTime } from '../../../../common/log_entry'; +import { LogEntryTime } from '@kbn/logs-shared-plugin/common'; import { LogSearchButtons } from './log_search_buttons'; import { LogSearchInput } from './log_search_input'; diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index e503bdebafa03..67235c96a8bcc 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -24,21 +24,15 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBasicTableColumn } from '@elastic/eui'; import { EuiButtonIcon } from '@elastic/eui'; -import { MetricsExplorerView } from '../../../common/metrics_explorer_views'; -import type { InventoryView } from '../../../common/inventory_views'; -import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; -import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; +import { SavedViewOperations, SavedViewItem } from '../../../common/saved_views'; -type View = InventoryView | MetricsExplorerView; -type UseViewResult = UseInventoryViewsResult | UseMetricsExplorerViewsResult; - -export interface ManageViewsFlyoutProps { - views: UseViewResult['views']; +export interface ManageViewsFlyoutProps { + views?: SavedViewItem[]; loading: boolean; onClose(): void; - onMakeDefaultView: UseViewResult['setDefaultViewById']; - onSwitchView: UseViewResult['switchViewById']; - onDeleteView: UseViewResult['deleteViewById']; + onMakeDefaultView: SavedViewOperations['setDefaultViewById']; + onSwitchView: SavedViewOperations['switchViewById']; + onDeleteView: SavedViewOperations['deleteViewById']; } interface DeleteConfimationProps { @@ -50,18 +44,18 @@ const searchConfig = { box: { incremental: true }, }; -export function ManageViewsFlyout({ +export function ManageViewsFlyout({ onClose, views = [], onSwitchView, onMakeDefaultView, onDeleteView, loading, -}: ManageViewsFlyoutProps) { +}: ManageViewsFlyoutProps) { // Add name as top level property to allow in memory search const namedViews = useMemo(() => views.map(addOwnName), [views]); - const renderName = (name: string, item: View) => ( + const renderName = (name: string, item: SavedViewItem) => ( ); - const renderDeleteAction = (item: View) => { + const renderDeleteAction = (item: SavedViewItem) => { return ( { + const renderMakeDefaultAction = (item: SavedViewItem) => { return ( > = [ + const columns: Array> = [ { field: 'name', name: i18n.translate('xpack.infra.openView.columnNames.name', { defaultMessage: 'Name' }), @@ -193,4 +187,7 @@ const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => /** * Helpers */ -const addOwnName = (view: View) => ({ ...view, name: view.attributes.name }); +const addOwnName = (view: TSavedViewState) => ({ + ...view, + name: view.attributes.name, +}); diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index b52d83cac60c6..11d45a51a0b2c 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -10,35 +10,30 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { NonEmptyString } from '@kbn/io-ts-utils'; +import { + SavedViewState, + SavedViewOperations, + SavedViewItem, + BasicAttributes, +} from '../../../common/saved_views'; import { ManageViewsFlyout } from './manage_views_flyout'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; -import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; -import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; - -type UseViewProps = - | 'currentView' - | 'views' - | 'isFetchingViews' - | 'isFetchingCurrentView' - | 'isCreatingView' - | 'isUpdatingView'; - -type UseViewResult = UseInventoryViewsResult | UseMetricsExplorerViewsResult; -type InventoryViewsResult = Pick; -type MetricsExplorerViewsResult = Pick; - -interface Props extends InventoryViewsResult, MetricsExplorerViewsResult { - viewState: ViewState & { time?: number }; - onCreateView: UseViewResult['createView']; - onDeleteView: UseViewResult['deleteViewById']; - onUpdateView: UseViewResult['updateViewById']; - onLoadViews: UseViewResult['fetchViews']; - onSetDefaultView: UseViewResult['setDefaultViewById']; - onSwitchView: UseViewResult['switchViewById']; + +interface Props + extends SavedViewState { + viewState: TViewState & BasicAttributes; + onCreateView: SavedViewOperations['createView']; + onDeleteView: SavedViewOperations['deleteViewById']; + onUpdateView: SavedViewOperations['updateViewById']; + onLoadViews: SavedViewOperations['fetchViews']; + onSetDefaultView: SavedViewOperations['setDefaultViewById']; + onSwitchView: SavedViewOperations['switchViewById']; } -export function SavedViewsToolbarControls(props: Props) { +export function SavedViewsToolbarControls( + props: Props +) { const { currentView, views, diff --git a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts index 1ea8f71da129e..8327a14c28256 100644 --- a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts +++ b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts @@ -7,8 +7,7 @@ import { useState } from 'react'; import createContainer from 'constate'; -import { LogViewReference } from '../../../../common/log_views'; -import { LogEntry } from '../../../../common/log_entry'; +import { LogEntry, LogViewReference } from '@kbn/logs-shared-plugin/common'; interface ViewLogInContextProps { logViewReference: LogViewReference; diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 5bbc52e17afda..93873a307d59d 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -9,63 +9,29 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { - QueryObserverBaseResult, - UseMutateAsyncFunction, - UseMutateFunction, - useMutation, - useQuery, - useQueryClient, -} from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-shared-plugin/public'; -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; -import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; import { - CreateInventoryViewAttributesRequestPayload, - UpdateInventoryViewAttributesRequestPayload, -} from '../../common/http_api/latest'; + MutationContext, + SavedViewResult, + ServerError, + UpdateViewParams, +} from '../../common/saved_views'; +import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; +import { CreateInventoryViewAttributesRequestPayload } from '../../common/http_api/latest'; import type { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; -interface UpdateViewParams { - id: string; - attributes: UpdateInventoryViewAttributesRequestPayload; -} - -export interface UseInventoryViewsResult { - views?: InventoryView[]; - currentView?: InventoryView | null; - createView: UseMutateAsyncFunction< - InventoryView, - ServerError, - CreateInventoryViewAttributesRequestPayload - >; - deleteViewById: UseMutateFunction; - fetchViews: QueryObserverBaseResult['refetch']; - updateViewById: UseMutateAsyncFunction; - switchViewById: (id: InventoryViewId) => void; - setDefaultViewById: UseMutateFunction< - MetricsSourceConfigurationResponse, - ServerError, - string, - MutationContext - >; - isCreatingView: boolean; - isFetchingCurrentView: boolean; - isFetchingViews: boolean; - isUpdatingView: boolean; -} - -type ServerError = IHttpFetchError; - -interface MutationContext { - id?: string; - previousViews?: InventoryView[]; -} +export type UseInventoryViewsResult = SavedViewResult< + InventoryView, + InventoryViewId, + CreateInventoryViewAttributesRequestPayload, + MetricsSourceConfigurationResponse +>; const queryKeys = { find: ['inventory-views-find'] as const, @@ -122,7 +88,7 @@ export const useInventoryViews = (): UseInventoryViewsResult => { MetricsSourceConfigurationResponse, ServerError, string, - MutationContext + MutationContext >({ mutationFn: (id) => updateSourceConfiguration({ inventoryDefaultView: id }), /** @@ -167,7 +133,7 @@ export const useInventoryViews = (): UseInventoryViewsResult => { const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< InventoryView, ServerError, - UpdateViewParams + UpdateViewParams >({ mutationFn: ({ id, attributes }) => inventoryViews.client.updateInventoryView(id, attributes), onError: (error) => { @@ -178,7 +144,12 @@ export const useInventoryViews = (): UseInventoryViewsResult => { }, }); - const { mutate: deleteViewById } = useMutation({ + const { mutate: deleteViewById } = useMutation< + null, + ServerError, + string, + MutationContext + >({ mutationFn: (id: string) => inventoryViews.client.deleteInventoryView(id), /** * To provide a quick feedback, we perform an optimistic update on the list diff --git a/x-pack/plugins/infra/public/hooks/use_log_view.mock.ts b/x-pack/plugins/infra/public/hooks/use_log_view.mock.ts deleted file mode 100644 index 3d95dfb72abb0..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_log_view.mock.ts +++ /dev/null @@ -1,77 +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 { interpret } from 'xstate'; -import { createLogViewMock } from '../../common/log_views/log_view.mock'; -import { createResolvedLogViewMockFromAttributes } from '../../common/log_views/resolved_log_view.mock'; -import { - createLogViewNotificationChannel, - createPureLogViewStateMachine, -} from '../observability_logs/log_view_state/src'; -import { useLogView } from './use_log_view'; - -type UseLogView = typeof useLogView; -type IUseLogView = ReturnType; - -const defaultLogViewId = 'default'; - -export const createUninitializedUseLogViewMock = - (logViewId: string = defaultLogViewId) => - (): IUseLogView => ({ - derivedDataView: undefined, - hasFailedLoading: false, - hasFailedLoadingLogView: false, - hasFailedLoadingLogViewStatus: false, - hasFailedResolvingLogView: false, - isLoading: false, - isLoadingLogView: false, - isLoadingLogViewStatus: false, - isResolvingLogView: false, - isUninitialized: true, - latestLoadLogViewFailures: [], - load: jest.fn(), - retry: jest.fn(), - logView: undefined, - logViewReference: { type: 'log-view-reference', logViewId }, - logViewStatus: undefined, - resolvedLogView: undefined, - update: jest.fn(), - changeLogViewReference: jest.fn(), - revertToDefaultLogView: jest.fn(), - logViewStateService: interpret( - createPureLogViewStateMachine({ logViewReference: { type: 'log-view-reference', logViewId } }) - ), - logViewStateNotifications: createLogViewNotificationChannel(), - isPersistedLogView: false, - isInlineLogView: false, - }); - -export const createLoadingUseLogViewMock = - (logViewId: string = defaultLogViewId) => - (): IUseLogView => ({ - ...createUninitializedUseLogViewMock(logViewId)(), - isLoading: true, - isLoadingLogView: true, - isLoadingLogViewStatus: true, - isResolvingLogView: true, - }); - -export const createLoadedUseLogViewMock = async (logViewId: string = defaultLogViewId) => { - const logView = createLogViewMock(logViewId); - const resolvedLogView = await createResolvedLogViewMockFromAttributes(logView.attributes); - - return (): IUseLogView => { - return { - ...createUninitializedUseLogViewMock(logViewId)(), - logView, - resolvedLogView, - logViewStatus: { - index: 'available', - }, - }; - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts index 210a23a3b21ef..f2c9500f9ea63 100644 --- a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts @@ -9,63 +9,29 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { - QueryObserverBaseResult, - UseMutateAsyncFunction, - UseMutateFunction, - useMutation, - useQuery, - useQueryClient, -} from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-shared-plugin/public'; -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; -import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; import { - CreateMetricsExplorerViewAttributesRequestPayload, - UpdateMetricsExplorerViewAttributesRequestPayload, -} from '../../common/http_api/latest'; + MutationContext, + SavedViewResult, + ServerError, + UpdateViewParams, +} from '../../common/saved_views'; +import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; +import { CreateMetricsExplorerViewAttributesRequestPayload } from '../../common/http_api/latest'; import { MetricsExplorerView } from '../../common/metrics_explorer_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; -interface UpdateViewParams { - id: string; - attributes: UpdateMetricsExplorerViewAttributesRequestPayload; -} - -export interface UseMetricsExplorerViewsResult { - views?: MetricsExplorerView[]; - currentView?: MetricsExplorerView | null; - createView: UseMutateAsyncFunction< - MetricsExplorerView, - ServerError, - CreateMetricsExplorerViewAttributesRequestPayload - >; - deleteViewById: UseMutateFunction; - fetchViews: QueryObserverBaseResult['refetch']; - updateViewById: UseMutateAsyncFunction; - switchViewById: (id: MetricsExplorerViewId) => void; - setDefaultViewById: UseMutateFunction< - MetricsSourceConfigurationResponse, - ServerError, - string, - MutationContext - >; - isCreatingView: boolean; - isFetchingCurrentView: boolean; - isFetchingViews: boolean; - isUpdatingView: boolean; -} - -type ServerError = IHttpFetchError; - -interface MutationContext { - id?: string; - previousViews?: MetricsExplorerView[]; -} +export type UseMetricsExplorerViewsResult = SavedViewResult< + MetricsExplorerView, + MetricsExplorerViewId, + CreateMetricsExplorerViewAttributesRequestPayload, + MetricsSourceConfigurationResponse +>; const queryKeys = { find: ['metrics-explorer-views-find'] as const, @@ -122,7 +88,7 @@ export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { MetricsSourceConfigurationResponse, ServerError, string, - MutationContext + MutationContext >({ mutationFn: (id) => updateSourceConfiguration({ metricsExplorerDefaultView: id }), /** @@ -167,7 +133,7 @@ export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< MetricsExplorerView, ServerError, - UpdateViewParams + UpdateViewParams >({ mutationFn: ({ id, attributes }) => metricsExplorerViews.client.updateMetricsExplorerView(id, attributes), @@ -179,7 +145,12 @@ export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { }, }); - const { mutate: deleteViewById } = useMutation({ + const { mutate: deleteViewById } = useMutation< + null, + ServerError, + string, + MutationContext + >({ mutationFn: (id: string) => metricsExplorerViews.client.deleteMetricsExplorerView(id), /** * To provide a quick feedback, we perform an optimistic update on the list diff --git a/x-pack/plugins/infra/public/index.ts b/x-pack/plugins/infra/public/index.ts index 4bd094b3d79f4..49a0257a22b38 100644 --- a/x-pack/plugins/infra/public/index.ts +++ b/x-pack/plugins/infra/public/index.ts @@ -29,6 +29,4 @@ export { InfraFormatterType } from './lib/lib'; export type InfraAppId = 'logs' | 'metrics'; // Shared components -export { LazyLogStreamWrapper as LogStream } from './components/log_stream/lazy_log_stream_wrapper'; -export type { LogStreamProps } from './components/log_stream'; export type { InfraClientStartExports } from './types'; diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index a37a9af7d9320..ac6527b9cc9c3 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -7,14 +7,16 @@ import { i18n } from '@kbn/i18n'; import * as rt from 'io-ts'; -import { +import type { InventoryMapBounds } from '../../common/inventory_views'; +import type { InfraTimerangeInput, SnapshotGroupBy, SnapshotMetricInput, SnapshotNodeMetric, SnapshotNodePath, } from '../../common/http_api/snapshot_api'; -import { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options'; +import type { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options'; +export type { InventoryColorPalette } from '../../common/inventory_views'; export interface InfraWaffleMapNode { pathId: string; @@ -72,9 +74,6 @@ export const PALETTES = { }), }; -export const InventoryColorPaletteRT = rt.keyof(PALETTES); -export type InventoryColorPalette = rt.TypeOf; - export const StepRuleRT = rt.intersection([ rt.type({ value: rt.number, @@ -136,10 +135,7 @@ export interface InfraOptions { wafflemap: InfraWaffleMapOptions; } -export interface InfraWaffleMapBounds { - min: number; - max: number; -} +export type InfraWaffleMapBounds = InventoryMapBounds; export type InfraFormatter = (value: string | number) => string; export enum InfraFormatterType { diff --git a/x-pack/plugins/infra/public/mocks.tsx b/x-pack/plugins/infra/public/mocks.tsx index d977e13ddd980..9b232c515ce81 100644 --- a/x-pack/plugins/infra/public/mocks.tsx +++ b/x-pack/plugins/infra/public/mocks.tsx @@ -8,14 +8,12 @@ import React from 'react'; import { createLocatorMock } from '../common/locators/locators.mock'; import { createInventoryViewsServiceStartMock } from './services/inventory_views/inventory_views_service.mock'; -import { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock'; import { createMetricsExplorerViewsServiceStartMock } from './services/metrics_explorer_views/metrics_explorer_views_service.mock'; import { createTelemetryServiceMock } from './services/telemetry/telemetry_service.mock'; import { InfraClientStartExports } from './types'; export const createInfraPluginStartMock = () => ({ inventoryViews: createInventoryViewsServiceStartMock(), - logViews: createLogViewsServiceStartMock(), metricsExplorerViews: createMetricsExplorerViewsServiceStartMock(), telemetry: createTelemetryServiceMock(), locators: createLocatorMock(), diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/state_machine.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/state_machine.ts index b7c02e916dc57..d2a71c65702eb 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/state_machine.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/state_machine.ts @@ -8,7 +8,8 @@ import { RefreshInterval } from '@kbn/data-plugin/public'; import { TimeRange } from '@kbn/es-query'; import { actions, ActorRefFrom, createMachine, EmittedFrom } from 'xstate'; -import { DEFAULT_REFRESH_INTERVAL } from '../../../../../common/log_views'; +import { DEFAULT_REFRESH_INTERVAL } from '@kbn/logs-shared-plugin/common'; +import type { LogViewNotificationChannel } from '@kbn/logs-shared-plugin/public'; import { datemathToEpochMillis } from '../../../../utils/datemath'; import { createLogStreamPositionStateMachine } from '../../../log_stream_position_state/src/state_machine'; import { @@ -16,7 +17,6 @@ import { DEFAULT_TIMERANGE, LogStreamQueryStateMachineDependencies, } from '../../../log_stream_query_state'; -import type { LogViewNotificationChannel } from '../../../log_view_state'; import { OmitDeprecatedState } from '../../../xstate_helpers'; import { waitForInitialQueryParameters, diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/types.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/types.ts index eb42dccdf2486..12c707ebb91fd 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/types.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_page/state/src/types.ts @@ -6,8 +6,13 @@ */ import { TimeRange } from '@kbn/es-query'; +import type { LogViewStatus } from '@kbn/logs-shared-plugin/common'; +import type { + LogViewContextWithError, + LogViewContextWithResolvedLogView, + LogViewNotificationEvent, +} from '@kbn/logs-shared-plugin/public'; import { TimeKey } from '../../../../../common/time'; -import type { LogViewStatus } from '../../../../../common/log_views'; import { JumpToTargetPositionEvent, LogStreamPositionContext, @@ -22,11 +27,6 @@ import { UpdateTimeRangeEvent, } from '../../../log_stream_query_state'; import { LogStreamQueryNotificationEvent } from '../../../log_stream_query_state/src/notifications'; -import type { - LogViewContextWithError, - LogViewContextWithResolvedLogView, - LogViewNotificationEvent, -} from '../../../log_view_state'; export interface ReceivedInitialQueryParametersEvent { type: 'RECEIVED_INITIAL_QUERY_PARAMETERS'; diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/url_state_storage_service.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/url_state_storage_service.ts index 5607bf9207054..c98ab9e147444 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/url_state_storage_service.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/url_state_storage_service.ts @@ -10,6 +10,7 @@ import { IKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugi import * as Either from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/function'; import { InvokeCreator } from 'xstate'; +import { replaceStateKeyInQueryString } from '../../../../common/url_state_storage_service'; import { minimalTimeKeyRT, pickTimeKey } from '../../../../common/time'; import { createPlainError, formatErrors } from '../../../../common/runtime_types'; import type { LogStreamPositionContext, LogStreamPositionEvent } from './types'; @@ -97,3 +98,13 @@ export type PositionStateInUrl = rt.TypeOf; const decodePositionQueryValueFromUrl = (queryValueFromUrl: unknown) => { return positionStateInUrlRT.decode(queryValueFromUrl); }; + +export const replaceLogPositionInQueryString = (time?: number) => + Number.isNaN(time) || time == null + ? (value: string) => value + : replaceStateKeyInQueryString(defaultPositionStateKey, { + position: { + time, + tiebreaker: 0, + }, + }); 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 1a69642037d19..1c0de464121c8 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 @@ -14,7 +14,7 @@ import type { import { EsQueryConfig } from '@kbn/es-query'; import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { actions, ActorRefFrom, createMachine, SpecialTargets, send } from 'xstate'; -import { DEFAULT_REFRESH_INTERVAL } from '../../../../common/log_views'; +import { DEFAULT_REFRESH_INTERVAL } from '@kbn/logs-shared-plugin/common'; import { OmitDeprecatedState, sendIfDefined } from '../../xstate_helpers'; import { logStreamQueryNotificationEventSelectors } from './notifications'; import { diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/url_state_storage_service.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/url_state_storage_service.ts index 448fb941e037c..fb65fcd987a11 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/url_state_storage_service.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/url_state_storage_service.ts @@ -16,9 +16,11 @@ import { defaultFilterStateKey, defaultPositionStateKey, DEFAULT_REFRESH_INTERVAL, - getTimeRangeStartFromTime, +} from '@kbn/logs-shared-plugin/common'; +import { getTimeRangeEndFromTime, -} from '../../../../common/log_views'; + getTimeRangeStartFromTime, +} from '../../../../common/url_state_storage_service'; import { minimalTimeKeyRT } from '../../../../common/time'; import { datemathStringRT } from '../../../utils/datemath'; import { createPlainError, formatErrors } from '../../../../common/runtime_types'; diff --git a/x-pack/plugins/infra/public/observability_logs/xstate_helpers/src/state_machine_playground.tsx b/x-pack/plugins/infra/public/observability_logs/xstate_helpers/src/state_machine_playground.tsx index 78f1c5f15dee9..5a40bd5d32292 100644 --- a/x-pack/plugins/infra/public/observability_logs/xstate_helpers/src/state_machine_playground.tsx +++ b/x-pack/plugins/infra/public/observability_logs/xstate_helpers/src/state_machine_playground.tsx @@ -7,7 +7,7 @@ import { EuiButton } from '@elastic/eui'; import React, { useCallback } from 'react'; -import { useLogViewContext } from '../../../hooks/use_log_view'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; export const StateMachinePlayground = () => { const { changeLogViewReference, revertToDefaultLogView, update, isLoading, logViewStateService } = diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index 3bbe00c5314cf..663df4c0f4d1a 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { DEFAULT_LOG_VIEW } from '../../../common/log_views'; +import { DEFAULT_LOG_VIEW } from '@kbn/logs-shared-plugin/common'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 66898bf38b4b4..e3382d43c0e15 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { DEFAULT_LOG_VIEW } from '../../../common/log_views'; +import { DEFAULT_LOG_VIEW } from '@kbn/logs-shared-plugin/common'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index bde85aa99ec7f..d6a1e9f2ddc5e 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React, { useCallback, useEffect } from 'react'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { @@ -22,7 +23,6 @@ import { import { SubscriptionSplashPage } from '../../../components/subscription_splash_content'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { LogsPageTemplate } from '../shared/page_template'; import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx index 6e1d3f4a4f0bd..5cb6a12166c53 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx @@ -6,12 +6,12 @@ */ import React from 'react'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { InlineLogViewSplashPage } from '../../../components/logging/inline_log_view_splash_page'; import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { ConnectedLogViewErrorPage } from '../shared/page_log_view_error'; export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index f26f8768a4459..e1e317136deed 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -15,6 +15,7 @@ import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { MLJobsAwaitingNodeWarning, ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { TimeRange } from '../../../../common/time/time_range'; import { CategoryJobNoticesSection } from '../../../components/logging/log_analysis_job_status'; import { AnalyzeInMlButton } from '../../../components/logging/log_analysis_results'; @@ -24,7 +25,6 @@ import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_ import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { ViewLogInContextProvider } from '../../../containers/logs/view_log_in_context'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { LogsPageTemplate } from '../shared/page_template'; import { PageViewLogInContext } from '../stream/page_view_log_in_context'; import { TopCategoriesSection } from './sections/top_categories'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx index dbf143f95d0bc..34cced7d92ffd 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect } from 'react'; -import { PersistedLogViewReference } from '../../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { useLogEntryCategoryExamples } from '../../use_log_entry_category_examples'; import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples'; import { TimeRange } from '../../../../../../common/time/time_range'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx index 96c0a23ac755e..0811ec1708530 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx @@ -5,28 +5,27 @@ * 2.0. */ -import React, { useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; +import { LogEntry, LogEntryContext } from '@kbn/logs-shared-plugin/common'; +import { + LogEntryColumn, + LogEntryContextMenu, + LogEntryFieldColumn, + LogEntryMessageColumn, + LogEntryRowWrapper, + LogEntryTimestampColumn, +} from '@kbn/logs-shared-plugin/public'; +import { useLinkProps, useUiTracker } from '@kbn/observability-shared-plugin/public'; import { encode } from '@kbn/rison'; import moment from 'moment'; - -import { useUiTracker, useLinkProps } from '@kbn/observability-shared-plugin/public'; -import { LogEntry, LogEntryContext } from '../../../../../../common/log_entry'; -import { TimeRange } from '../../../../../../common/time'; +import React, { useCallback, useState } from 'react'; import { getFriendlyNameForPartitionId, partitionField, } from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time'; import { useViewLogInProviderContext } from '../../../../../containers/logs/view_log_in_context'; -import { - LogEntryColumn, - LogEntryFieldColumn, - LogEntryMessageColumn, - LogEntryRowWrapper, - LogEntryTimestampColumn, -} from '../../../../../components/logging/log_text_stream'; import { LogColumnConfiguration } from '../../../../../utils/source_configuration'; -import { LogEntryContextMenu } from '../../../../../components/logging/log_text_stream/log_entry_context_menu'; export const exampleMessageScale = 'medium' as const; export const exampleTimestampFormat = 'dateTime' as const; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx index c9f93ee618ddb..6dd07a80c8652 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -9,7 +9,7 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { PersistedLogViewReference } from '../../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { LogEntryCategory } from '../../../../../../common/log_analysis'; import { TimeRange } from '../../../../../../common/time'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx index 3d06a212fe581..f119a08cbd10a 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx @@ -12,7 +12,7 @@ import React, { useMemo, useCallback } from 'react'; import useSet from 'react-use/lib/useSet'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { PersistedLogViewReference } from '../../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { LogEntryCategory, LogEntryCategoryDataset, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts index db17c0b866b7b..14e7ebfbebd35 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getLogEntryCategoryDatasetsRequestPayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts index a27e734235c3b..3e4947ca1e84f 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getLogEntryCategoryExamplesRequestPayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts index 5104ad897c880..c4a1b6d095a29 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getLogEntryCategoriesRequestPayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts index 7ef8d57b29d9f..20f7adb106857 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts @@ -7,7 +7,7 @@ import { useMemo, useState } from 'react'; -import { PersistedLogViewReference } from '../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { GetLogEntryCategoriesSuccessResponsePayload, GetLogEntryCategoryDatasetsSuccessResponsePayload, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx index 8152cae426448..c5516fdbc02f9 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx @@ -6,7 +6,7 @@ */ import { useMemo, useState } from 'react'; -import { PersistedLogViewReference } from '../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { LogEntryCategoryExample } from '../../../../common/http_api'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index 1f6fe04e59161..533381dcbf7c3 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import React, { memo, useCallback, useEffect } from 'react'; import useInterval from 'react-use/lib/useInterval'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { @@ -24,7 +25,6 @@ import { SubscriptionSplashPage } from '../../../components/subscription_splash_ import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { LogsPageTemplate } from '../shared/page_template'; import { LogEntryRateResultsContent } from './page_results_content'; import { LogEntryRateSetupContent } from './page_setup_content'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx index 7e1381cbd4f21..46ce90cff63cc 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { InlineLogViewSplashPage } from '../../../components/logging/inline_log_view_splash_page'; import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout'; import { SourceLoadingPage } from '../../../components/source_loading_page'; @@ -13,7 +14,6 @@ import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_a import { LogEntryRateModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_rate'; import { LogEntryFlyoutProvider } from '../../../containers/logs/log_flyout'; import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { ConnectedLogViewErrorPage } from '../shared/page_log_view_error'; export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index b535c1fd155de..a4d861e38ade1 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -14,6 +14,7 @@ import { encode } from '@kbn/rison'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { MLJobsAwaitingNodeWarning } from '@kbn/ml-plugin/public'; import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; +import { useLogViewContext, LogEntryFlyout } from '@kbn/logs-shared-plugin/public'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { TimeKey } from '../../../../common/time'; import { @@ -23,12 +24,10 @@ import { import { DatasetsSelector } from '../../../components/logging/log_analysis_results/datasets_selector'; import { ManageJobsButton } from '../../../components/logging/log_analysis_setup/manage_jobs_button'; import { useLogAnalysisSetupFlyoutStateContext } from '../../../components/logging/log_analysis_setup/setup_flyout'; -import { LogEntryFlyout } from '../../../components/logging/log_entry_flyout'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis/log_analysis_capabilities'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate'; import { useLogEntryFlyoutContext } from '../../../containers/logs/log_flyout'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { LogsPageTemplate } from '../shared/page_template'; import { AnomaliesResults } from './sections/anomalies'; import { useDatasetFiltering } from './use_dataset_filtering'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 493b5c4077c69..5fe150e3c2724 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -11,10 +11,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import useMount from 'react-use/lib/useMount'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { isCategoryAnomaly, LogEntryAnomaly } from '../../../../../../common/log_analysis'; import { TimeRange } from '../../../../../../common/time/time_range'; import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples'; -import { useLogViewContext } from '../../../../../hooks/use_log_view'; import { useLogEntryExamples } from '../../use_log_entry_examples'; import { LogEntryExampleMessage, LogEntryExampleMessageHeaders } from './log_entry_example'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx index 5b23f5f9e2a45..288fc3df39c8e 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx @@ -12,8 +12,6 @@ import { i18n } from '@kbn/i18n'; import { useMlHref, ML_PAGES } from '@kbn/ml-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useLinkProps, shouldHandleLinkEvent } from '@kbn/observability-shared-plugin/public'; -import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; -import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; import { LogEntryColumn, LogEntryFieldColumn, @@ -23,11 +21,11 @@ import { LogEntryContextMenu, LogEntryColumnWidths, iconColumnId, -} from '../../../../../components/logging/log_text_stream'; -import { LogColumnHeadersWrapper, LogColumnHeader, -} from '../../../../../components/logging/log_text_stream/column_headers'; +} from '@kbn/logs-shared-plugin/public'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; +import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; import { TimeRange } from '../../../../../../common/time/time_range'; import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; import { LogEntryExample, isCategoryAnomaly } from '../../../../../../common/log_analysis'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts index dc8ab4772473d..b6a515ae6f134 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getLogEntryAnomaliesRequestPayloadRT, getLogEntryAnomaliesSuccessReponsePayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts index 093a906008582..a93712c5d5a86 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { decodeOrThrow } from '../../../../../common/runtime_types'; import { getLogEntryAnomaliesDatasetsRequestPayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts index f6d90692ad470..6cf35b95868e1 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts @@ -6,7 +6,7 @@ */ import type { HttpHandler } from '@kbn/core/public'; -import { PersistedLogViewReference } from '../../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { getLogEntryExamplesRequestPayloadRT, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts index db4fc77964173..745b5a7cd5aec 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts @@ -7,7 +7,7 @@ import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; import useMount from 'react-use/lib/useMount'; -import { PersistedLogViewReference } from '../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; import { callGetLogEntryAnomaliesAPI } from './service_calls/get_log_entry_anomalies'; import { callGetLogEntryAnomaliesDatasetsAPI } from './service_calls/get_log_entry_anomalies_datasets'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts index a678f5deaf07a..4301f08ab41da 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts @@ -6,7 +6,7 @@ */ import { useMemo, useState } from 'react'; -import { PersistedLogViewReference } from '../../../../common/log_views'; +import { PersistedLogViewReference } from '@kbn/logs-shared-plugin/common'; import { LogEntryExample } from '../../../../common/log_analysis'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/pages/logs/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/page_providers.tsx index e185a62ea9646..f4112b7bd0a05 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_providers.tsx @@ -6,21 +6,21 @@ */ import React, { useState } from 'react'; -import { LogAnalysisCapabilitiesProvider } from '../../containers/logs/log_analysis'; -import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; -import { LogViewProvider } from '../../hooks/use_log_view'; import { + LogViewProvider, initializeFromUrl as createInitializeFromUrl, updateContextInUrl as createUpdateContextInUrl, listenForUrlChanges as createListenForUrlChanges, -} from '../../observability_logs/log_view_state/src/url_state_storage_service'; +} from '@kbn/logs-shared-plugin/public'; +import { LogAnalysisCapabilitiesProvider } from '../../containers/logs/log_analysis'; +import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { useKbnUrlStateStorageFromRouterContext } from '../../utils/kbn_url_state_context'; export const LogsPageProviders: React.FunctionComponent = ({ children }) => { const { services: { notifications: { toasts: toastsService }, - logViews: { client }, + logsShared, }, } = useKibanaContextForPlugin(); @@ -38,7 +38,7 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { return ( { return ( - + {({ buckets, start, end }) => ( )} - + ); }} @@ -293,6 +299,16 @@ export const StreamPageLogsContent = React.memo<{ ); }); +const WithSummaryAndQuery = (props: Omit) => { + const serializedParsedQuery = useSelector(useLogStreamPageStateContext(), (logStreamPageState) => + logStreamPageState.matches({ hasLogViewIndices: 'initialized' }) + ? stringify(logStreamPageState.context.parsedQuery) + : null + ); + + return ; +}; + type InitializedLogStreamPageState = MatchedStateFromActor< LogStreamPageActorRef, { hasLogViewIndices: 'initialized' } diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx index 0a9f5ae9395e3..9b058bd03acd3 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -7,20 +7,21 @@ import stringify from 'json-stable-stringify'; import React, { useMemo } from 'react'; +import { + LogHighlightsStateProvider, + LogPositionStateProvider, + LogStreamProvider, + useLogPositionStateContext, + useLogStreamContext, + useLogViewContext, +} from '@kbn/logs-shared-plugin/public'; import { LogStreamPageActorRef, LogStreamPageCallbacks, } from '../../../observability_logs/log_stream_page/state'; import { LogEntryFlyoutProvider } from '../../../containers/logs/log_flyout'; -import { LogHighlightsStateProvider } from '../../../containers/logs/log_highlights/log_highlights'; -import { - LogPositionStateProvider, - useLogPositionStateContext, -} from '../../../containers/logs/log_position'; -import { LogStreamProvider, useLogStreamContext } from '../../../containers/logs/log_stream'; import { LogViewConfigurationProvider } from '../../../containers/logs/log_view_configuration'; import { ViewLogInContextProvider } from '../../../containers/logs/view_log_in_context'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { MatchedStateFromActor } from '../../../observability_logs/xstate_helpers'; const ViewLogInContext: React.FC = ({ children }) => { diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 99c9d498fc6cc..9ea8e60eef0b9 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -8,15 +8,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; +import { + useLogHighlightsStateContext, + useLogPositionStateContext, + useLogViewContext, +} from '@kbn/logs-shared-plugin/public'; import { LogCustomizationMenu } from '../../../components/logging/log_customization_menu'; import { LogHighlightsMenu } from '../../../components/logging/log_highlights_menu'; import { LogTextScaleControls } from '../../../components/logging/log_text_scale_controls'; import { LogTextWrapControls } from '../../../components/logging/log_text_wrap_controls'; -import { useLogHighlightsStateContext } from '../../../containers/logs/log_highlights/log_highlights'; -import { useLogPositionStateContext } from '../../../containers/logs/log_position'; import { useLogViewConfigurationContext } from '../../../containers/logs/log_view_configuration'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -import { useLogViewContext } from '../../../hooks/use_log_view'; import { StreamLiveButton } from './components/stream_live_button'; export const LogsToolbar = () => { diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 15dbbcca7ce9f..2917b7b21d78f 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -17,10 +17,10 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { isEmpty } from 'lodash'; import React, { useCallback, useMemo } from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { LogEntry } from '../../../../common/log_entry'; +import { LogEntry } from '@kbn/logs-shared-plugin/common'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import { useViewLogInProviderContext } from '../../../containers/logs/view_log_in_context'; import { useViewportDimensions } from '../../../utils/use_viewport_dimensions'; -import { LogStream } from '../../../components/log_stream'; const MODAL_MARGIN = 25; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx index cf409e878cb71..0c1d254342b84 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx @@ -85,13 +85,14 @@ export const Tile = ({ const filters = useMemo(() => { return [ + ...searchCriteria.filters, buildCombinedHostsFilter({ field: 'host.name', values: hostNodes.map((p) => p.name), dataView, }), ]; - }, [hostNodes, dataView]); + }, [searchCriteria.filters, hostNodes, dataView]); const handleBrushEnd = useCallback( ({ range }: BrushTriggerEvent['data']) => { @@ -122,9 +123,10 @@ export const Tile = ({ () => getExtraActions({ timeRange: afterLoadedState.dateRange, + query: searchCriteria.query, filters, }), - [afterLoadedState.dateRange, filters, getExtraActions] + [afterLoadedState.dateRange, filters, getExtraActions, searchCriteria.query] ); return ( @@ -168,6 +170,7 @@ export const Tile = ({ lastReloadRequestTime={afterLoadedState.lastReloadRequestTime} dateRange={afterLoadedState.dateRange} filters={afterLoadedState.filters} + query={afterLoadedState.query} onBrushEnd={handleBrushEnd} loading={loading} /> diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx index ac3981026ea8e..cd2537418e46c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { LogViewReference } from '../../../../../../../common/log_views'; +import { LogViewReference } from '@kbn/logs-shared-plugin/common'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; interface LogsLinkToStreamProps { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx index 6fe796d33e909..66cbc9d1c1b1b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx @@ -8,8 +8,8 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import { InfraLoadingPanel } from '../../../../../../components/loading'; -import { LogStream } from '../../../../../../components/log_stream'; import { useHostsViewContext } from '../../../hooks/use_hosts_view'; import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state'; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx index 51a0a4ea30931..5d7e946f25e2a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx @@ -65,21 +65,23 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => const filters = useMemo(() => { return [ + ...searchCriteria.filters, buildCombinedHostsFilter({ field: 'host.name', values: currentPage.map((p) => p.name), dataView, }), ]; - }, [currentPage, dataView]); + }, [currentPage, dataView, searchCriteria.filters]); const extraActions: Action[] = useMemo( () => getExtraActions({ timeRange: afterLoadedState.dateRange, + query: afterLoadedState.query, filters, }), - [afterLoadedState.dateRange, filters, getExtraActions] + [afterLoadedState.dateRange, afterLoadedState.query, filters, getExtraActions] ); const handleBrushEnd = useCallback( @@ -137,6 +139,7 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => lastReloadRequestTime={afterLoadedState.lastReloadRequestTime} dateRange={afterLoadedState.dateRange} filters={filters} + query={afterLoadedState.query} onBrushEnd={handleBrushEnd} loading={loading} hasTitle diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_log_view_reference.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_log_view_reference.ts index 7335855189526..46a062001bb74 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_log_view_reference.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_log_view_reference.ts @@ -8,8 +8,8 @@ // import { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { v4 as uuidv4 } from 'uuid'; +import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common'; import { useLazyRef } from '../../../../hooks/use_lazy_ref'; -import { DEFAULT_LOG_VIEW, type LogViewReference } from '../../../../../common/log_views'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; interface Props { @@ -18,13 +18,11 @@ interface Props { } export const useLogViewReference = ({ id, extraFields = [] }: Props) => { const { - services: { - logViews: { client }, - }, + services: { logsShared }, } = useKibanaContextForPlugin(); const { loading, value: defaultLogView } = useAsync( - () => client.getLogView(DEFAULT_LOG_VIEW), + () => logsShared.logViews.client.getLogView(DEFAULT_LOG_VIEW), [] ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx index 3024ff3d1bd06..ddbf4ae52788b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx @@ -14,9 +14,9 @@ import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiButtonEmpty } from '@elastic/eui'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; import { TabContent, TabProps } from './shared'; -import { LogStream } from '../../../../../../components/log_stream'; import { useWaffleOptionsContext } from '../../../hooks/use_waffle_options'; import { findInventoryFields } from '../../../../../../../common/inventory_models'; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx index 4547b0dbb0147..feb5283a39dcb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { useInventoryViews } from '../../../../hooks/use_inventory_views'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; -import { useWaffleViewState, WaffleViewState } from '../hooks/use_waffle_view_state'; +import { useWaffleViewState } from '../hooks/use_waffle_view_state'; export const SavedViews = () => { const { viewState } = useWaffleViewState(); @@ -28,7 +28,7 @@ export const SavedViews = () => { } = useInventoryViews(); return ( - + { return true; }; -export const DEFAULT_WAFFLE_FILTERS_STATE: WaffleFiltersState = { kind: 'kuery', expression: '' }; +export const DEFAULT_WAFFLE_FILTERS_STATE: InventoryFiltersState = { + kind: 'kuery', + expression: '', +}; export const useWaffleFilters = () => { const { createDerivedIndexPattern } = useSourceContext(); const indexPattern = createDerivedIndexPattern(); - const [urlState, setUrlState] = useUrlState({ + const [urlState, setUrlState] = useUrlState({ defaultState: DEFAULT_WAFFLE_FILTERS_STATE, decodeUrlState, encodeUrlState, urlStateKey: 'waffleFilter', }); - const [state, setState] = useState(urlState); + const [state, setState] = useState(urlState); useEffect(() => setUrlState(state), [setUrlState, state]); @@ -61,7 +68,7 @@ export const useWaffleFilters = () => { [setState] ); - const applyFilterQuery = useCallback((filterQuery: WaffleFiltersState) => { + const applyFilterQuery = useCallback((filterQuery: InventoryFiltersState) => { setState(filterQuery); setFilterQueryDraft(filterQuery.expression); }, []); @@ -87,14 +94,10 @@ export const useWaffleFilters = () => { }; }; -export const WaffleFiltersStateRT = rt.type({ - kind: rt.literal('kuery'), - expression: rt.string, -}); - -export type WaffleFiltersState = rt.TypeOf; -const encodeUrlState = WaffleFiltersStateRT.encode; +// temporary +export type WaffleFiltersState = InventoryFiltersState; +const encodeUrlState = inventoryFiltersStateRT.encode; const decodeUrlState = (value: unknown) => - pipe(WaffleFiltersStateRT.decode(value), fold(constant(undefined), identity)); + pipe(inventoryFiltersStateRT.decode(value), fold(constant(undefined), identity)); export const WaffleFilters = createContainter(useWaffleFilters); export const [WaffleFiltersProvider, useWaffleFiltersContext] = WaffleFilters; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts index 8767be4f8a27e..9151e591a09f1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts @@ -6,23 +6,25 @@ */ import { useCallback, useState, useEffect } from 'react'; -import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import createContainer from 'constate'; -import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill'; -import { InventoryColorPaletteRT } from '../../../../lib/lib'; +import { InventoryViewOptions } from '../../../../../common/inventory_views/types'; import { + type InventoryLegendOptions, + type InventoryOptionsState, + type InventorySortOption, + inventoryOptionsStateRT, +} from '../../../../../common/inventory_views'; +import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill'; +import type { SnapshotMetricInput, SnapshotGroupBy, SnapshotCustomMetricInput, - SnapshotMetricInputRT, - SnapshotGroupByRT, - SnapshotCustomMetricInputRT, } from '../../../../../common/http_api/snapshot_api'; import { useUrlState } from '../../../../utils/use_url_state'; -import { InventoryItemType, ItemTypeRT } from '../../../../../common/inventory_models/types'; +import type { InventoryItemType } from '../../../../../common/inventory_models/types'; export const DEFAULT_LEGEND: WaffleLegendOptions = { palette: 'cool', @@ -75,7 +77,7 @@ export const useWaffleOptions = () => { ); const changeView = useCallback( - (view: string) => setState((previous) => ({ ...previous, view })), + (view: string) => setState((previous) => ({ ...previous, view: view as InventoryViewOptions })), [setState] ); @@ -160,51 +162,15 @@ export const useWaffleOptions = () => { }; }; -const WaffleLegendOptionsRT = rt.type({ - palette: InventoryColorPaletteRT, - steps: rt.number, - reverseColors: rt.boolean, -}); - -export type WaffleLegendOptions = rt.TypeOf; - -export const WaffleSortOptionRT = rt.type({ - by: rt.keyof({ name: null, value: null }), - direction: rt.keyof({ asc: null, desc: null }), -}); - -export const WaffleOptionsStateRT = rt.intersection([ - rt.type({ - metric: SnapshotMetricInputRT, - groupBy: SnapshotGroupByRT, - nodeType: ItemTypeRT, - view: rt.string, - customOptions: rt.array( - rt.type({ - text: rt.string, - field: rt.string, - }) - ), - boundsOverride: rt.type({ - min: rt.number, - max: rt.number, - }), - autoBounds: rt.boolean, - accountId: rt.string, - region: rt.string, - customMetrics: rt.array(SnapshotCustomMetricInputRT), - sort: WaffleSortOptionRT, - }), - rt.partial({ source: rt.string, legend: WaffleLegendOptionsRT, timelineOpen: rt.boolean }), -]); - -export type WaffleSortOption = rt.TypeOf; -export type WaffleOptionsState = rt.TypeOf; -const encodeUrlState = (state: WaffleOptionsState) => { - return WaffleOptionsStateRT.encode(state); +export type WaffleLegendOptions = InventoryLegendOptions; +export type WaffleSortOption = InventorySortOption; +export type WaffleOptionsState = InventoryOptionsState; + +const encodeUrlState = (state: InventoryOptionsState) => { + return inventoryOptionsStateRT.encode(state); }; const decodeUrlState = (value: unknown) => { - const state = pipe(WaffleOptionsStateRT.decode(value), fold(constant(undefined), identity)); + const state = pipe(inventoryOptionsStateRT.decode(value), fold(constant(undefined), identity)); if (state) { state.source = 'url'; } diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index 6e685a6cc105f..c1ff4c67addbb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -6,17 +6,10 @@ */ import { useCallback } from 'react'; -import { - useWaffleOptionsContext, - DEFAULT_WAFFLE_OPTIONS_STATE, - WaffleOptionsState, -} from './use_waffle_options'; +import { InventoryViewAttributes } from '../../../../../common/inventory_views'; +import { useWaffleOptionsContext, DEFAULT_WAFFLE_OPTIONS_STATE } from './use_waffle_options'; import { useWaffleTimeContext, DEFAULT_WAFFLE_TIME_STATE } from './use_waffle_time'; -import { - useWaffleFiltersContext, - DEFAULT_WAFFLE_FILTERS_STATE, - WaffleFiltersState, -} from './use_waffle_filters'; +import { useWaffleFiltersContext, DEFAULT_WAFFLE_FILTERS_STATE } from './use_waffle_filters'; export const DEFAULT_WAFFLE_VIEW_STATE: WaffleViewState = { ...DEFAULT_WAFFLE_OPTIONS_STATE, @@ -65,8 +58,8 @@ export const useWaffleViewState = () => { }; const onViewChange = useCallback( - (newState) => { - const attributes = newState.attributes as WaffleViewState; + (newState: { attributes: WaffleViewState }) => { + const attributes = newState.attributes; setWaffleOptionsState({ sort: attributes.sort, @@ -102,8 +95,7 @@ export const useWaffleViewState = () => { }; }; -export type WaffleViewState = WaffleOptionsState & { - time: number; - autoReload: boolean; - filterQuery: WaffleFiltersState; -}; +export type WaffleViewState = Omit< + InventoryViewAttributes, + 'name' | 'isDefault' | 'isStatic' | 'source' +>; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_legend.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_legend.ts index c7015764ddf24..cd37b0d8aab25 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_legend.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_legend.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { InventoryColorPalette, InfraWaffleMapSteppedGradientLegend } from '../../../../lib/lib'; +import type { + InventoryColorPalette, + InfraWaffleMapSteppedGradientLegend, +} from '../../../../lib/lib'; import { getColorPalette } from './get_color_palette'; export const createLegend = ( diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts b/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts index 73b09017fecc0..9b48b9391f9bd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts @@ -13,7 +13,7 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { replaceStateKeyInQueryString } from '../../../../../common/log_views'; +import { replaceStateKeyInQueryString } from '../../../../../common/url_state_storage_service'; import { useUrlState } from '../../../../utils/use_url_state'; const parseRange = (range: MetricsTimeInput) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx index 2d329f121f008..ddce0eac506fe 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx @@ -31,7 +31,7 @@ export const SavedViews = ({ viewState }: Props) => { } = useMetricsExplorerViews(); return ( - + currentView={currentView} views={views} isFetchingViews={isFetchingViews} diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 36c873c3b22df..7ea1dbdfa7cd2 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -19,7 +19,6 @@ import { ObservabilityTriggerId } from '@kbn/observability-shared-plugin/common' import { BehaviorSubject, combineLatest, from } from 'rxjs'; import { map } from 'rxjs/operators'; import { DISCOVER_APP_TARGET, LOGS_APP_TARGET } from '../common/constants'; -import { defaultLogViewsStaticConfig } from '../common/log_views'; import { InfraPublicConfig } from '../common/plugin_config_types'; import { createInventoryMetricRuleType } from './alerting/inventory'; import { createLogThresholdRuleType } from './alerting/log_threshold'; @@ -39,7 +38,6 @@ import { import { createMetricsFetchData, createMetricsHasData } from './metrics_overview_fetchers'; import { registerFeatures } from './register_feature'; import { InventoryViewsService } from './services/inventory_views'; -import { LogViewsService } from './services/log_views'; import { MetricsExplorerViewsService } from './services/metrics_explorer_views'; import { TelemetryService } from './services/telemetry'; import { @@ -56,7 +54,6 @@ import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './utils/logs_ export class Plugin implements InfraClientPluginClass { public config: InfraPublicConfig; private inventoryViews: InventoryViewsService; - private logViews: LogViewsService; private metricsExplorerViews: MetricsExplorerViewsService; private telemetry: TelemetryService; private locators?: InfraLocators; @@ -68,10 +65,6 @@ export class Plugin implements InfraClientPluginClass { this.config = context.config.get(); this.inventoryViews = new InventoryViewsService(); - this.logViews = new LogViewsService({ - messageFields: - this.config.sources?.default?.fields?.message ?? defaultLogViewsStaticConfig.messageFields, - }); this.metricsExplorerViews = new MetricsExplorerViewsService(); this.telemetry = new TelemetryService(); this.appTarget = this.config.logs.app_target; @@ -106,6 +99,10 @@ export class Plugin implements InfraClientPluginClass { fetchData: createMetricsFetchData(core.getStartServices), }); + pluginsSetup.logsShared.logViews.setLogViewsStaticConfig({ + messageFields: this.config.sources?.default?.fields?.message, + }); + const startDep$AndHostViewFlag$ = combineLatest([ from(core.getStartServices()), core.uiSettings.get$(enableInfrastructureHostsView), @@ -342,12 +339,6 @@ export class Plugin implements InfraClientPluginClass { http: core.http, }); - const logViews = this.logViews.start({ - http: core.http, - dataViews: plugins.dataViews, - search: plugins.data.search, - }); - const metricsExplorerViews = this.metricsExplorerViews.start({ http: core.http, }); @@ -356,7 +347,6 @@ export class Plugin implements InfraClientPluginClass { const startContract: InfraClientStartExports = { inventoryViews, - logViews, metricsExplorerViews, telemetry, locators: this.locators!, diff --git a/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts index eeabd15e60a4b..5cfda02fa4c17 100644 --- a/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts @@ -9,15 +9,18 @@ import { HttpStart } from '@kbn/core/public'; import { CreateInventoryViewAttributesRequestPayload, createInventoryViewRequestPayloadRT, + CreateInventoryViewResponsePayload, + FindInventoryViewResponsePayload, findInventoryViewResponsePayloadRT, + GetInventoryViewResposePayload, getInventoryViewUrl, inventoryViewResponsePayloadRT, UpdateInventoryViewAttributesRequestPayload, + UpdateInventoryViewResponsePayload, } from '../../../common/http_api/latest'; import { DeleteInventoryViewError, FetchInventoryViewError, - InventoryView, UpsertInventoryViewError, } from '../../../common/inventory_views'; import { decodeOrThrow } from '../../../common/runtime_types'; @@ -26,7 +29,7 @@ import { IInventoryViewsClient } from './types'; export class InventoryViewsClient implements IInventoryViewsClient { constructor(private readonly http: HttpStart) {} - async findInventoryViews(): Promise { + async findInventoryViews(): Promise { const response = await this.http.get(getInventoryViewUrl()).catch((error) => { throw new FetchInventoryViewError(`Failed to fetch inventory views: ${error}`); }); @@ -40,7 +43,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { return data; } - async getInventoryView(inventoryViewId: string): Promise { + async getInventoryView(inventoryViewId: string): Promise { const response = await this.http.get(getInventoryViewUrl(inventoryViewId)).catch((error) => { throw new FetchInventoryViewError( `Failed to fetch inventory view "${inventoryViewId}": ${error}` @@ -60,7 +63,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { async createInventoryView( inventoryViewAttributes: CreateInventoryViewAttributesRequestPayload - ): Promise { + ): Promise { const response = await this.http .post(getInventoryViewUrl(), { body: JSON.stringify( @@ -85,7 +88,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { async updateInventoryView( inventoryViewId: string, inventoryViewAttributes: UpdateInventoryViewAttributesRequestPayload - ): Promise { + ): Promise { const response = await this.http .put(getInventoryViewUrl(inventoryViewId), { body: JSON.stringify( diff --git a/x-pack/plugins/infra/public/services/inventory_views/types.ts b/x-pack/plugins/infra/public/services/inventory_views/types.ts index 573c144e9c441..e2e26e6ef7f5b 100644 --- a/x-pack/plugins/infra/public/services/inventory_views/types.ts +++ b/x-pack/plugins/infra/public/services/inventory_views/types.ts @@ -8,9 +8,12 @@ import { HttpStart } from '@kbn/core/public'; import { CreateInventoryViewAttributesRequestPayload, + CreateInventoryViewResponsePayload, + FindInventoryViewResponsePayload, + GetInventoryViewResposePayload, UpdateInventoryViewAttributesRequestPayload, + UpdateInventoryViewResponsePayload, } from '../../../common/http_api/latest'; -import type { InventoryView } from '../../../common/inventory_views'; export type InventoryViewsServiceSetup = void; @@ -23,14 +26,14 @@ export interface InventoryViewsServiceStartDeps { } export interface IInventoryViewsClient { - findInventoryViews(): Promise; - getInventoryView(inventoryViewId: string): Promise; + findInventoryViews(): Promise; + getInventoryView(inventoryViewId: string): Promise; createInventoryView( inventoryViewAttributes: CreateInventoryViewAttributesRequestPayload - ): Promise; + ): Promise; updateInventoryView( inventoryViewId: string, inventoryViewAttributes: UpdateInventoryViewAttributesRequestPayload - ): Promise; + ): Promise; deleteInventoryView(inventoryViewId: string): Promise; } diff --git a/x-pack/plugins/infra/public/test_utils/entries.ts b/x-pack/plugins/infra/public/test_utils/entries.ts index 4dc3732fd49d5..35dad808cdf4f 100644 --- a/x-pack/plugins/infra/public/test_utils/entries.ts +++ b/x-pack/plugins/infra/public/test_utils/entries.ts @@ -6,16 +6,7 @@ */ import faker from 'faker'; -import { LogEntry } from '../../common/log_entry'; -import { LogViewColumnConfiguration } from '../../common/log_views'; - -export const ENTRIES_EMPTY = { - data: { - entries: [], - topCursor: null, - bottomCursor: null, - }, -}; +import { LogEntry, LogViewColumnConfiguration } from '@kbn/logs-shared-plugin/common'; export function generateFakeEntries( count: number, diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 5c78c1534a5a4..488d92573e746 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -38,6 +38,10 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; import { DiscoverStart } from '@kbn/discover-plugin/public'; import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { + LogsSharedClientSetupExports, + LogsSharedClientStartExports, +} from '@kbn/logs-shared-plugin/public'; import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { UnwrapPromise } from '../common/utility_types'; @@ -46,7 +50,6 @@ import type { UseNodeMetricsTableOptions, } from './components/infrastructure_node_metrics_tables/shared'; import { InventoryViewsServiceStart } from './services/inventory_views'; -import { LogViewsServiceStart } from './services/log_views'; import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; import { ITelemetryClient } from './services/telemetry'; import type { InfraLocators } from '../common/locators'; @@ -58,7 +61,6 @@ export interface InfraClientSetupExports { export interface InfraClientStartExports { inventoryViews: InventoryViewsServiceStart; - logViews: LogViewsServiceStart; metricsExplorerViews: MetricsExplorerViewsServiceStart; telemetry: ITelemetryClient; locators: InfraLocators; @@ -74,6 +76,7 @@ export interface InfraClientStartExports { } export interface InfraClientSetupDeps { + logsShared: LogsSharedClientSetupExports; home?: HomePublicPluginSetup; observability: ObservabilityPublicSetup; observabilityShared: ObservabilitySharedPluginSetup; @@ -97,6 +100,7 @@ export interface InfraClientStartDeps { embeddable?: EmbeddableStart; kibanaVersion?: string; lens: LensPublicStart; + logsShared: LogsSharedClientStartExports; ml: MlPluginStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts index 5ebacc0e7773b..186e4c9bc1ed0 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -12,7 +12,7 @@ import { FetchDataParams, LogsFetchDataResponse, } from '@kbn/observability-plugin/public'; -import { DEFAULT_LOG_VIEW } from '../../common/log_views'; +import { DEFAULT_LOG_VIEW } from '@kbn/logs-shared-plugin/common'; import { TIMESTAMP_FIELD } from '../../common/constants'; import { InfraClientStartDeps, InfraClientStartServicesAccessor } from '../types'; @@ -38,9 +38,11 @@ type StatsAndSeries = Pick; export function getLogsHasDataFetcher(getStartServices: InfraClientStartServicesAccessor) { return async () => { - const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW); - const logViewStatus = await logViews.client.getResolvedLogViewStatus(resolvedLogView); + const [, { logsShared }] = await getStartServices(); + const resolvedLogView = await logsShared.logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW); + const logViewStatus = await logsShared.logViews.client.getResolvedLogViewStatus( + resolvedLogView + ); const hasData = logViewStatus.index === 'available'; const indices = resolvedLogView.indices; @@ -56,8 +58,8 @@ export function getLogsOverviewDataFetcher( getStartServices: InfraClientStartServicesAccessor ): FetchData { return async (params) => { - const [, { data }, { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW); + const [, { data, logsShared }] = await getStartServices(); + const resolvedLogView = await logsShared.logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW); const { stats, series } = await fetchLogsOverview( { diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts index dad3dedc4bb46..85d7f14586913 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts @@ -6,10 +6,11 @@ */ import { CoreStart } from '@kbn/core/public'; -import { of } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { createResolvedLogViewMock } from '../../common/log_views/resolved_log_view.mock'; +import { createResolvedLogViewMock } from '@kbn/logs-shared-plugin/common/mocks'; +import { createLogsSharedPluginStartMock } from '@kbn/logs-shared-plugin/public/mocks'; +import { of } from 'rxjs'; import { createInfraPluginStartMock } from '../mocks'; import { InfraClientStartDeps, InfraClientStartExports } from '../types'; import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './logs_overview_fetchers'; @@ -24,10 +25,18 @@ const DEFAULT_PARAMS = { function setup() { const core = coreMock.createStart(); const data = dataPluginMock.createStartContract(); + const logsShared = createLogsSharedPluginStartMock(); const pluginStart = createInfraPluginStartMock(); - const pluginDeps = { data } as InfraClientStartDeps; + const pluginDeps = { data, logsShared } as unknown as InfraClientStartDeps; const dataSearch = data.search.search as jest.MockedFunction; + const getResolvedLogView = logsShared.logViews.client.getResolvedLogView as jest.MockedFunction< + typeof logsShared.logViews.client.getResolvedLogView + >; + const getResolvedLogViewStatus = logsShared.logViews.client + .getResolvedLogViewStatus as jest.MockedFunction< + typeof logsShared.logViews.client.getResolvedLogViewStatus + >; const mockedGetStartServices = jest.fn(() => Promise.resolve<[CoreStart, InfraClientStartDeps, InfraClientStartExports]>([ @@ -36,7 +45,15 @@ function setup() { pluginStart, ]) ); - return { core, dataSearch, mockedGetStartServices, pluginStart }; + return { + core, + dataSearch, + mockedGetStartServices, + pluginDeps, + pluginStart, + getResolvedLogView, + getResolvedLogViewStatus, + }; } describe('Logs UI Observability Homepage Functions', () => { @@ -46,62 +63,59 @@ describe('Logs UI Observability Homepage Functions', () => { describe('getLogsHasDataFetcher()', () => { it('should return true when non-empty indices exist', async () => { - const { mockedGetStartServices, pluginStart } = setup(); + const { mockedGetStartServices, pluginDeps, getResolvedLogView, getResolvedLogViewStatus } = + setup(); - pluginStart.logViews.client.getResolvedLogView.mockResolvedValue( - createResolvedLogViewMock({ indices: 'test-index' }) - ); - pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({ - index: 'available', - }); + getResolvedLogView.mockResolvedValue(createResolvedLogViewMock({ indices: 'test-index' })); + getResolvedLogViewStatus.mockResolvedValue({ index: 'available' }); const hasData = getLogsHasDataFetcher(mockedGetStartServices); const response = await hasData(); - expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1); + expect(pluginDeps.logsShared.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes( + 1 + ); expect(response).toEqual({ hasData: true, indices: 'test-index' }); }); it('should return false when only empty indices exist', async () => { - const { mockedGetStartServices, pluginStart } = setup(); + const { mockedGetStartServices, pluginDeps, getResolvedLogView, getResolvedLogViewStatus } = + setup(); - pluginStart.logViews.client.getResolvedLogView.mockResolvedValue( - createResolvedLogViewMock({ indices: 'test-index' }) - ); - pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({ - index: 'empty', - }); + getResolvedLogView.mockResolvedValue(createResolvedLogViewMock({ indices: 'test-index' })); + getResolvedLogViewStatus.mockResolvedValue({ index: 'empty' }); const hasData = getLogsHasDataFetcher(mockedGetStartServices); const response = await hasData(); - expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1); + expect(pluginDeps.logsShared.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes( + 1 + ); expect(response).toEqual({ hasData: false, indices: 'test-index' }); }); it('should return false when no index exists', async () => { - const { mockedGetStartServices, pluginStart } = setup(); + const { mockedGetStartServices, pluginDeps, getResolvedLogView, getResolvedLogViewStatus } = + setup(); - pluginStart.logViews.client.getResolvedLogView.mockResolvedValue( - createResolvedLogViewMock({ indices: 'test-index' }) - ); - pluginStart.logViews.client.getResolvedLogViewStatus.mockResolvedValue({ - index: 'missing', - }); + getResolvedLogView.mockResolvedValue(createResolvedLogViewMock({ indices: 'test-index' })); + getResolvedLogViewStatus.mockResolvedValue({ index: 'missing' }); const hasData = getLogsHasDataFetcher(mockedGetStartServices); const response = await hasData(); - expect(pluginStart.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes(1); + expect(pluginDeps.logsShared.logViews.client.getResolvedLogViewStatus).toHaveBeenCalledTimes( + 1 + ); expect(response).toEqual({ hasData: false, indices: 'test-index' }); }); }); describe('getLogsOverviewDataFetcher()', () => { it('should work', async () => { - const { mockedGetStartServices, dataSearch, pluginStart } = setup(); + const { mockedGetStartServices, dataSearch, getResolvedLogView } = setup(); - pluginStart.logViews.client.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); + getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); dataSearch.mockReturnValue( of({ diff --git a/x-pack/plugins/infra/public/utils/url_state.tsx b/x-pack/plugins/infra/public/utils/url_state.tsx index 8fc4961bba221..a07b8afbc68f8 100644 --- a/x-pack/plugins/infra/public/utils/url_state.tsx +++ b/x-pack/plugins/infra/public/utils/url_state.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { Route } from '@kbn/shared-ux-router'; import { decode, RisonValue } from '@kbn/rison'; import { throttle } from 'lodash'; -import { replaceStateKeyInQueryString } from '../../common/log_views'; +import { replaceStateKeyInQueryString } from '../../common/url_state_storage_service'; interface UrlStateContainerProps { urlState: UrlState | undefined; diff --git a/x-pack/plugins/infra/public/utils/use_url_state.ts b/x-pack/plugins/infra/public/utils/use_url_state.ts index 3d269f9eb058c..8fc03d2d9dda2 100644 --- a/x-pack/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/plugins/infra/public/utils/use_url_state.ts @@ -10,7 +10,7 @@ import { Location } from 'history'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { decode, RisonValue } from '@kbn/rison'; import { useHistory } from 'react-router-dom'; -import { replaceStateKeyInQueryString } from '../../common/log_views'; +import { replaceStateKeyInQueryString } from '../../common/url_state_storage_service'; export const useUrlState = ({ defaultState, diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index a1b1a7b729193..e9fec4a5b4f5d 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import { logViewSavedObjectName } from '@kbn/logs-shared-plugin/server'; import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../common/alerting/logs/log_threshold/types'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, @@ -14,7 +15,6 @@ import { } from '../common/alerting/metrics'; import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants'; import { infraSourceConfigurationSavedObjectName } from './lib/sources/saved_object_type'; -import { logViewSavedObjectName } from './saved_objects'; export const METRICS_FEATURE = { id: METRICS_FEATURE_ID, diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index 4d29974ceb75f..e54713faba76a 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -22,12 +22,6 @@ import { initValidateLogAnalysisDatasetsRoute, initValidateLogAnalysisIndicesRoute, } from './routes/log_analysis'; -import { - initLogEntriesHighlightsRoute, - initLogEntriesSummaryHighlightsRoute, - initLogEntriesSummaryRoute, -} from './routes/log_entries'; -import { initLogViewRoutes } from './routes/log_views'; import { initMetadataRoute } from './routes/metadata'; import { initMetricsAPIRoute } from './routes/metrics_api'; import { initMetricExplorerRoute } from './routes/metrics_explorer'; @@ -55,10 +49,6 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initValidateLogAnalysisDatasetsRoute(libs); initValidateLogAnalysisIndicesRoute(libs); initGetLogEntryExamplesRoute(libs); - initLogEntriesHighlightsRoute(libs); - initLogEntriesSummaryRoute(libs); - initLogEntriesSummaryHighlightsRoute(libs); - initLogViewRoutes(libs); initMetricExplorerRoute(libs); initMetricsExplorerViewRoutes(libs); initMetricsAPIRoute(libs); diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 22351750fbab4..61c8b935806ac 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -24,6 +24,7 @@ import { PluginSetupContract as AlertingPluginContract } from '@kbn/alerting-plu import { MlPluginSetup } from '@kbn/ml-plugin/server'; import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; +import { LogsSharedPluginSetup, LogsSharedPluginStart } from '@kbn/logs-shared-plugin/server'; import { VersionedRouteConfig } from '@kbn/core-http-server'; export interface InfraServerPluginSetupDeps { @@ -38,11 +39,13 @@ export interface InfraServerPluginSetupDeps { usageCollection: UsageCollectionSetup; visTypeTimeseries: VisTypeTimeseriesSetup; ml?: MlPluginSetup; + logsShared: LogsSharedPluginSetup; } export interface InfraServerPluginStartDeps { data: DataPluginStart; dataViews: DataViewsPluginStart; + logsShared: LogsSharedPluginStart; } export interface CallWithRequestParams extends estypes.RequestBase { 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 index 0860da5c7a184..d0a301726138e 100644 --- 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 @@ -29,6 +29,7 @@ import { createInventoryMetricThresholdExecutor } from './inventory_metric_thres import { ConditionResult } from './evaluate_condition'; import { InfraBackendLibs } from '../../infra_types'; import { infraPluginMock } from '../../../mocks'; +import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); @@ -121,7 +122,7 @@ const mockLibs = { }, getStartServices: () => [ null, - infraPluginMock.createSetupContract(), + { logsShared: logsSharedPluginMock.createStartContract() }, infraPluginMock.createStartContract(), ], configuration: createMockStaticConfiguration({}), 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 5a1dba9faae14..0754c79a99688 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 @@ -162,8 +162,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - const [, , { logViews }] = await libs.getStartServices(); - const logQueryFields: LogQueryFields | undefined = await logViews + const [, { logsShared }] = await libs.getStartServices(); + const logQueryFields: LogQueryFields | undefined = await logsShared.logViews .getClient(savedObjectsClient, esClient) .getResolvedLogView({ type: 'log-view-reference', diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts index 97c43ea578e73..f54334ef22e5c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { ResolvedLogView } from '@kbn/logs-shared-plugin/common'; import { ExecutionTimeRange, GroupedSearchQueryResponse, @@ -19,7 +20,6 @@ import { Point, Series, } from '../../../../common/http_api'; -import { ResolvedLogView } from '../../../../common/log_views'; import { decodeOrThrow } from '../../../../common/runtime_types'; import type { InfraPluginRequestHandlerContext } from '../../../types'; import { KibanaFramework } from '../../adapters/framework/kibana_framework_adapter'; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index 7091fc62a2bba..9b93e021ae8f1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -201,12 +201,12 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => return alert; }; - const [, , { logViews }] = await libs.getStartServices(); + const [, { logsShared }] = await libs.getStartServices(); try { const validatedParams = decodeOrThrow(ruleParamsRT)(params); - const { indices, timestampField, runtimeMappings } = await logViews + const { indices, timestampField, runtimeMappings } = await logsShared.logViews .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser) .getResolvedLogView(validatedParams.logView); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts index 7a44311b40fcd..12b67b6f260e4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts @@ -6,9 +6,9 @@ */ import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; -import { logViewSavedObjectName } from '../../../saved_objects'; +import { logViewReferenceRT } from '@kbn/logs-shared-plugin/common'; +import { logViewSavedObjectName } from '@kbn/logs-shared-plugin/server'; import { RuleParams, ruleParamsRT } from '../../../../common/alerting/logs/log_threshold'; -import { logViewReferenceRT } from '../../../../common/log_views'; import { decodeOrThrow } from '../../../../common/runtime_types'; export const LOG_VIEW_REFERENCE_NAME = 'log-view-reference-0'; diff --git a/x-pack/plugins/infra/server/lib/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts index f9fe976f692a5..e31bad1b5ffeb 100644 --- a/x-pack/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/infra/server/lib/infra_types.ts @@ -10,18 +10,18 @@ import type { IBasePath } from '@kbn/core/server'; import type { handleEsError } from '@kbn/es-ui-shared-plugin/server'; import type { AlertsLocatorParams } from '@kbn/observability-plugin/common'; import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { ILogsSharedLogEntriesDomain } from '@kbn/logs-shared-plugin/server'; import { RulesServiceSetup } from '../services/rules'; import { InfraConfig, InfraPluginStartServicesAccessor } from '../types'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; import { InfraFieldsDomain } from './domains/fields_domain'; -import { InfraLogEntriesDomain } from './domains/log_entries_domain'; import { InfraMetricsDomain } from './domains/metrics_domain'; import { InfraSources } from './sources'; import { InfraSourceStatus } from './source_status'; export interface InfraDomainLibs { fields: InfraFieldsDomain; - logEntries: InfraLogEntriesDomain; + logEntries: ILogsSharedLogEntriesDomain; metrics: InfraMetricsDomain; } diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index b17afa68d2d4d..591376450be38 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -6,6 +6,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { PersistedLogViewReference, ResolvedLogView } from '@kbn/logs-shared-plugin/common'; import { AnomaliesSort, getJobId, @@ -16,7 +17,6 @@ import { logEntryRateJobTypes, Pagination, } from '../../../common/log_analysis'; -import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index d8eb18e4890b5..88678f4c79c53 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -7,6 +7,11 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core/server'; +import { + LogEntryContext, + PersistedLogViewReference, + ResolvedLogView, +} from '@kbn/logs-shared-plugin/common'; import { CategoriesSort, compareDatasetsByMaximumAnomalyScore, @@ -14,8 +19,6 @@ import { jobCustomSettingsRT, logEntryCategoriesJobTypes, } from '../../../common/log_analysis'; -import { LogEntryContext } from '../../../common/log_entry'; -import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; diff --git a/x-pack/plugins/infra/server/mocks.ts b/x-pack/plugins/infra/server/mocks.ts index 7e5a349cb1e01..a15575572a076 100644 --- a/x-pack/plugins/infra/server/mocks.ts +++ b/x-pack/plugins/infra/server/mocks.ts @@ -5,18 +5,21 @@ * 2.0. */ -import { createInventoryViewsServiceStartMock } from './services/inventory_views/inventory_views_service.mock'; import { - createLogViewsServiceSetupMock, - createLogViewsServiceStartMock, -} from './services/log_views/log_views_service.mock'; -import { createMetricsExplorerViewsServiceStartMock } from './services/metrics_explorer_views/metrics_explorer_views_service.mock'; + createInventoryViewsServiceSetupMock, + createInventoryViewsServiceStartMock, +} from './services/inventory_views/inventory_views_service.mock'; +import { + createMetricsExplorerViewsServiceSetupMock, + createMetricsExplorerViewsServiceStartMock, +} from './services/metrics_explorer_views/metrics_explorer_views_service.mock'; import { InfraPluginSetup, InfraPluginStart } from './types'; const createInfraSetupMock = () => { const infraSetupMock: jest.Mocked = { defineInternalSourceConfiguration: jest.fn(), - logViews: createLogViewsServiceSetupMock(), + inventoryViews: createInventoryViewsServiceSetupMock(), + metricsExplorerViews: createMetricsExplorerViewsServiceSetupMock(), }; return infraSetupMock; @@ -26,7 +29,6 @@ const createInfraStartMock = () => { const infraStartMock: jest.Mocked = { getMetricIndices: jest.fn(), inventoryViews: createInventoryViewsServiceStartMock(), - logViews: createLogViewsServiceStartMock(), metricsExplorerViews: createMetricsExplorerViewsServiceStartMock(), }; return infraStartMock; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 04fea926a2083..2449293dfa3aa 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -24,7 +24,6 @@ import { LOGS_FEATURE_ID, METRICS_FEATURE_ID, } from '../common/constants'; -import { defaultLogViewsStaticConfig } from '../common/log_views'; import { publicConfigKeys } from '../common/plugin_config_types'; import { configDeprecations, getInfraDeprecationsFactory } from './deprecations'; import { LOGS_FEATURE, METRICS_FEATURE } from './features'; @@ -32,7 +31,6 @@ import { initInfraServer } from './infra_server'; import { FrameworkFieldsAdapter } from './lib/adapters/fields/framework_fields_adapter'; import { InfraServerPluginSetupDeps, InfraServerPluginStartDeps } from './lib/adapters/framework'; import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; -import { InfraKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter'; import { KibanaMetricsAdapter } from './lib/adapters/metrics/kibana_metrics_adapter'; import { InfraElasticsearchSourceStatusAdapter } from './lib/adapters/source_status'; import { registerRuleTypes } from './lib/alerting'; @@ -41,20 +39,13 @@ import { METRICS_RULES_ALERT_CONTEXT, } from './lib/alerting/register_rule_types'; import { InfraFieldsDomain } from './lib/domains/fields_domain'; -import { InfraLogEntriesDomain } from './lib/domains/log_entries_domain'; import { InfraMetricsDomain } from './lib/domains/metrics_domain'; import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types'; import { makeGetMetricIndices } from './lib/metrics/make_get_metric_indices'; import { infraSourceConfigurationSavedObjectType, InfraSources } from './lib/sources'; import { InfraSourceStatus } from './lib/source_status'; -import { - inventoryViewSavedObjectType, - logViewSavedObjectType, - metricsExplorerViewSavedObjectType, -} from './saved_objects'; +import { inventoryViewSavedObjectType, metricsExplorerViewSavedObjectType } from './saved_objects'; import { InventoryViewsService } from './services/inventory_views'; -import { LogEntriesService } from './services/log_entries'; -import { LogViewsService } from './services/log_views'; import { MetricsExplorerViewsService } from './services/metrics_explorer_views'; import { RulesService } from './services/rules'; import { @@ -65,6 +56,7 @@ import { InfraPluginStart, } from './types'; import { UsageCollector } from './usage/usage_collector'; +import { mapSourceToLogView } from './utils/map_source_to_log_view'; export const config: PluginConfigDescriptor = { schema: schema.object({ @@ -138,7 +130,6 @@ export class InfraServerPlugin private logsRules: RulesService; private metricsRules: RulesService; private inventoryViews: InventoryViewsService; - private logViews: LogViewsService; private metricsExplorerViews: MetricsExplorerViewsService; constructor(context: PluginInitializerContext) { @@ -157,7 +148,6 @@ export class InfraServerPlugin ); this.inventoryViews = new InventoryViewsService(this.logger.get('inventoryViews')); - this.logViews = new LogViewsService(this.logger.get('logViews')); this.metricsExplorerViews = new MetricsExplorerViewsService( this.logger.get('metricsExplorerViews') ); @@ -170,18 +160,16 @@ export class InfraServerPlugin }); const sourceStatus = new InfraSourceStatus( new InfraElasticsearchSourceStatusAdapter(framework), - { - sources, - } + { sources } ); + + // Setup infra services const inventoryViews = this.inventoryViews.setup(); - const logViews = this.logViews.setup(); const metricsExplorerViews = this.metricsExplorerViews.setup(); - // register saved object types + // Register saved object types core.savedObjects.registerType(infraSourceConfigurationSavedObjectType); core.savedObjects.registerType(inventoryViewSavedObjectType); - core.savedObjects.registerType(logViewSavedObjectType); core.savedObjects.registerType(metricsExplorerViewSavedObjectType); // TODO: separate these out individually and do away with "domains" as a temporary group @@ -191,10 +179,7 @@ export class InfraServerPlugin fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { sources, }), - logEntries: new InfraLogEntriesDomain(new InfraKibanaLogEntriesAdapter(framework), { - framework, - getStartServices: () => core.getStartServices(), - }), + logEntries: plugins.logsShared.logEntries, metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)), }; @@ -216,6 +201,19 @@ export class InfraServerPlugin plugins.features.registerKibanaFeature(METRICS_FEATURE); plugins.features.registerKibanaFeature(LOGS_FEATURE); + // Register an handler to retrieve the fallback logView starting from a source configuration + plugins.logsShared.logViews.registerLogViewFallbackHandler(async (sourceId, { soClient }) => { + const sourceConfiguration = await sources.getSourceConfiguration(soClient, sourceId); + return mapSourceToLogView(sourceConfiguration); + }); + plugins.logsShared.logViews.setLogViewsStaticConfig({ + messageFields: this.config.sources?.default?.fields?.message, + }); + + plugins.logsShared.registerUsageCollectorActions({ + countLogs: () => UsageCollector.countLogs(), + }); + plugins.home.sampleData.addAppLinksToSampleDataset('logs', [ { sampleObject: null, // indicates that there is no sample object associated with this app link's path @@ -247,9 +245,6 @@ export class InfraServerPlugin // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); - const logEntriesService = new LogEntriesService(); - logEntriesService.setup(core, plugins); - // register deprecated source configuration fields core.deprecations.registerDeprecations({ getDeprecations: getInfraDeprecationsFactory(sources), @@ -258,29 +253,16 @@ export class InfraServerPlugin return { defineInternalSourceConfiguration: sources.defineInternalSourceConfiguration.bind(sources), inventoryViews, - logViews, metricsExplorerViews, } as InfraPluginSetup; } - start(core: CoreStart, plugins: InfraServerPluginStartDeps) { + start(core: CoreStart) { const inventoryViews = this.inventoryViews.start({ infraSources: this.libs.sources, savedObjects: core.savedObjects, }); - const logViews = this.logViews.start({ - infraSources: this.libs.sources, - savedObjects: core.savedObjects, - dataViews: plugins.dataViews, - elasticsearch: core.elasticsearch, - config: { - messageFields: - this.config.sources?.default?.fields?.message ?? - defaultLogViewsStaticConfig.messageFields, - }, - }); - const metricsExplorerViews = this.metricsExplorerViews.start({ infraSources: this.libs.sources, savedObjects: core.savedObjects, @@ -288,7 +270,6 @@ export class InfraServerPlugin return { inventoryViews, - logViews, metricsExplorerViews, getMetricIndices: makeGetMetricIndices(this.libs.sources), }; diff --git a/x-pack/plugins/infra/server/routes/inventory_views/README.md b/x-pack/plugins/infra/server/routes/inventory_views/README.md index be7d1c3734157..6edf5a540536a 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/README.md +++ b/x-pack/plugins/infra/server/routes/inventory_views/README.md @@ -130,26 +130,62 @@ Status code: 404 Creates a new inventory view. +`metric.type`: `"count" | "cpu" | "diskLatency" | "diskSpaceUsage" | "load" | "memory" | "memoryFree" | "memoryTotal" | "normalizedLoad1m" | "tx" | "rx" | "logRate" | "diskIOReadBytes" | "diskIOWriteBytes" | "s3TotalRequests" | "s3NumberOfObjects" | "s3BucketSize" | "s3DownloadBytes" | "s3UploadBytes" | "rdsConnections" | "rdsQueriesExecuted" | "rdsActiveTransactions" | "rdsLatency" | "sqsMessagesVisible" | "sqsMessagesDelayed" | "sqsMessagesSent" | "sqsMessagesEmpty" | "sqsOldestMessage"` + +`boundsOverride.max`: `range 0 to 1` +`boundsOverride.min`: `range 0 to 1` + +`sort.by`: `"name" | "value"` +`sort.direction`: `"asc | "desc"` + +`legend.pallete`: `"status" | "temperature" | "cool" | "warm" | "positive" | "negative"` + +`view`: `"map" | "table"` + ### Request - **Method**: POST - **Path**: /api/infra/inventory_views - **Request body**: + ```json { "attributes": { - "name": "View name", "metric": { - "type": "cpu" + "type": "cpu" }, "sort": { - "by": "name", - "direction": "desc" + "by": "name", + "direction": "desc" }, - //... + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "steps": 10, + "reverseColors": false + }, + "timelineOpen": false, + "name": "test-uptime" } } - ``` + +``` ### Response diff --git a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts index 9a963c3f84e72..1c6be6bf56c28 100644 --- a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts +++ b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts @@ -38,8 +38,10 @@ export const initGetLogAlertsChartPreviewDataRoute = ({ data: { logView, buckets, alertParams, executionTimeRange }, } = request.body; - const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); + const [, { logsShared }] = await getStartServices(); + const resolvedLogView = await logsShared.logViews + .getScopedClient(request) + .getResolvedLogView(logView); try { const { series } = await getChartPreviewData( diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts index b17a50d23974d..5e9a57768828c 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts @@ -45,8 +45,10 @@ export const initGetLogEntryCategoryExamplesRoute = ({ }, } = request.body; - const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); + const [, { logsShared }] = await getStartServices(); + const resolvedLogView = await logsShared.logViews + .getScopedClient(request) + .getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts index 2b413dce7f294..8b3b2f0449c58 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts @@ -46,8 +46,10 @@ export const initGetLogEntryExamplesRoute = ({ }, } = request.body; - const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); + const [, { logsShared }] = await getStartServices(); + const resolvedLogView = await logsShared.logViews + .getScopedClient(request) + .getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 0c893171b5b67..085be4584e2a7 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -40,8 +40,8 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { const soClient = (await requestContext.core).savedObjects.client; const source = await libs.sources.getSourceConfiguration(soClient, snapshotRequest.sourceId); const compositeSize = libs.configuration.inventory.compositeSize; - const [, , { logViews }] = await libs.getStartServices(); - const logQueryFields: LogQueryFields | undefined = await logViews + const [, { logsShared }] = await libs.getStartServices(); + const logQueryFields: LogQueryFields | undefined = await logsShared.logViews .getScopedClient(request) .getResolvedLogView({ type: 'log-view-reference', diff --git a/x-pack/plugins/infra/server/saved_objects/index.ts b/x-pack/plugins/infra/server/saved_objects/index.ts index cf6906fc733f7..c64f4b46808c4 100644 --- a/x-pack/plugins/infra/server/saved_objects/index.ts +++ b/x-pack/plugins/infra/server/saved_objects/index.ts @@ -6,5 +6,4 @@ */ export * from './inventory_view'; -export * from './log_view'; export * from './metrics_explorer_view'; diff --git a/x-pack/plugins/infra/server/saved_objects/inventory_view/types.ts b/x-pack/plugins/infra/server/saved_objects/inventory_view/types.ts index 45e738f3920f1..30ab7068b7f40 100644 --- a/x-pack/plugins/infra/server/saved_objects/inventory_view/types.ts +++ b/x-pack/plugins/infra/server/saved_objects/inventory_view/types.ts @@ -5,14 +5,76 @@ * 2.0. */ -import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; +import { inRangeRt, isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { ItemTypeRT } from '../../../common/inventory_models/types'; + +export const inventorySavedObjectColorPaletteRT = rt.keyof({ + status: null, + temperature: null, + cool: null, + warm: null, + positive: null, + negative: null, +}); + +const inventorySavedObjectLegendOptionsRT = rt.type({ + palette: inventorySavedObjectColorPaletteRT, + steps: inRangeRt(2, 18), + reverseColors: rt.boolean, +}); + +export const inventorySavedObjectSortOptionRT = rt.type({ + by: rt.keyof({ name: null, value: null }), + direction: rt.keyof({ asc: null, desc: null }), +}); + +export const inventorySavedObjectViewOptionsRT = rt.keyof({ table: null, map: null }); + +export const inventorySabedObjectMapBoundsRT = rt.type({ + min: inRangeRt(0, 1), + max: inRangeRt(0, 1), +}); + +export const inventorySavedObjectFiltersStateRT = rt.type({ + kind: rt.literal('kuery'), + expression: rt.string, +}); + +export const inventorySavedObjectOptionsStateRT = rt.intersection([ + rt.type({ + accountId: rt.string, + autoBounds: rt.boolean, + boundsOverride: inventorySabedObjectMapBoundsRT, + customMetrics: rt.UnknownArray, + customOptions: rt.array( + rt.type({ + text: rt.string, + field: rt.string, + }) + ), + groupBy: rt.UnknownArray, + metric: rt.UnknownRecord, + nodeType: ItemTypeRT, + region: rt.string, + sort: inventorySavedObjectSortOptionRT, + view: inventorySavedObjectViewOptionsRT, + }), + rt.partial({ + legend: inventorySavedObjectLegendOptionsRT, + source: rt.string, + timelineOpen: rt.boolean, + }), +]); export const inventoryViewSavedObjectAttributesRT = rt.intersection([ - rt.strict({ + inventorySavedObjectOptionsStateRT, + rt.type({ name: nonEmptyStringRt, + autoReload: rt.boolean, + filterQuery: inventorySavedObjectFiltersStateRT, }), - rt.UnknownRecord, + rt.partial({ time: rt.number, isDefault: rt.boolean, isStatic: rt.boolean }), ]); export const inventoryViewSavedObjectRT = rt.intersection([ @@ -25,3 +87,5 @@ export const inventoryViewSavedObjectRT = rt.intersection([ updated_at: isoToEpochRt, }), ]); + +export type InventoryViewSavedObject = rt.TypeOf; diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts index 5efce009da410..78c7007ed1d1a 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts @@ -14,6 +14,7 @@ import { } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { + inventoryViewAttributesRT, staticInventoryViewAttributes, staticInventoryViewId, } from '../../../common/inventory_views'; @@ -131,10 +132,10 @@ export class InventoryViewsClient implements IInventoryViewsClient { return this.savedObjectsClient.delete(inventoryViewSavedObjectName, inventoryViewId); } - private mapSavedObjectToInventoryView( - savedObject: SavedObject | SavedObjectsUpdateResponse, + private mapSavedObjectToInventoryView( + savedObject: SavedObject | SavedObjectsUpdateResponse, defaultViewId?: string - ) { + ): InventoryView { const inventoryViewSavedObject = decodeOrThrow(inventoryViewSavedObjectRT)(savedObject); return { @@ -142,7 +143,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { version: inventoryViewSavedObject.version, updatedAt: inventoryViewSavedObject.updated_at, attributes: { - ...inventoryViewSavedObject.attributes, + ...decodeOrThrow(inventoryViewAttributesRT)(inventoryViewSavedObject.attributes), isDefault: inventoryViewSavedObject.id === defaultViewId, isStatic: false, }, diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 49dbca9b276b2..0a4ad94c09d43 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -14,9 +14,11 @@ import type { SearchRequestHandlerContext } from '@kbn/data-plugin/server'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import type { InfraStaticSourceConfiguration } from '../common/source_configuration/source_configuration'; import { InfraServerPluginStartDeps } from './lib/adapters/framework'; -import { InventoryViewsServiceStart } from './services/inventory_views'; -import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; -import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; +import { InventoryViewsServiceSetup, InventoryViewsServiceStart } from './services/inventory_views'; +import { + MetricsExplorerViewsServiceSetup, + MetricsExplorerViewsServiceStart, +} from './services/metrics_explorer_views'; export type { InfraConfig } from '../common/plugin_config_types'; @@ -28,12 +30,12 @@ export interface InfraPluginSetup { sourceId: string, sourceProperties: InfraStaticSourceConfiguration ) => void; - logViews: LogViewsServiceSetup; + inventoryViews: InventoryViewsServiceSetup; + metricsExplorerViews: MetricsExplorerViewsServiceSetup; } export interface InfraPluginStart { inventoryViews: InventoryViewsServiceStart; - logViews: LogViewsServiceStart; metricsExplorerViews: MetricsExplorerViewsServiceStart; getMetricIndices: ( savedObjectsClient: SavedObjectsClientContract, diff --git a/x-pack/plugins/infra/server/utils/elasticsearch_runtime_types.ts b/x-pack/plugins/infra/server/utils/elasticsearch_runtime_types.ts index e2dbf02ae2d06..20f0aeb2e2f0a 100644 --- a/x-pack/plugins/infra/server/utils/elasticsearch_runtime_types.ts +++ b/x-pack/plugins/infra/server/utils/elasticsearch_runtime_types.ts @@ -17,8 +17,6 @@ export const shardFailureRT = rt.partial({ shard: rt.number, }); -export type ShardFailure = rt.TypeOf; - export const commonSearchSuccessResponseFieldsRT = rt.type({ _shards: rt.intersection([ rt.type({ @@ -34,8 +32,3 @@ export const commonSearchSuccessResponseFieldsRT = rt.type({ timed_out: rt.boolean, took: rt.number, }); - -export const commonHitFieldsRT = rt.type({ - _index: rt.string, - _id: rt.string, -}); diff --git a/x-pack/plugins/infra/server/utils/map_source_to_log_view.test.ts b/x-pack/plugins/infra/server/utils/map_source_to_log_view.test.ts new file mode 100644 index 0000000000000..38db985a28280 --- /dev/null +++ b/x-pack/plugins/infra/server/utils/map_source_to_log_view.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InfraSource } from '../lib/sources'; +import { getAttributesFromSourceConfiguration } from './map_source_to_log_view'; + +describe('getAttributesFromSourceConfiguration function', () => { + it('converts the index_pattern log indices type to data_view', () => { + const logViewAttributes = getAttributesFromSourceConfiguration(basicTestSourceConfiguration); + + expect(logViewAttributes.logIndices).toEqual({ + type: 'data_view', + dataViewId: 'INDEX_PATTERN_ID', + }); + }); + + it('preserves the index_name log indices type', () => { + const logViewAttributes = getAttributesFromSourceConfiguration({ + ...basicTestSourceConfiguration, + configuration: { + ...basicTestSourceConfiguration.configuration, + logIndices: { + type: 'index_name', + indexName: 'INDEX_NAME', + }, + }, + }); + + expect(logViewAttributes.logIndices).toEqual({ + type: 'index_name', + indexName: 'INDEX_NAME', + }); + }); +}); + +const basicTestSourceConfiguration: InfraSource = { + id: 'ID', + origin: 'stored', + configuration: { + name: 'NAME', + description: 'DESCRIPTION', + logIndices: { + type: 'index_pattern', + indexPatternId: 'INDEX_PATTERN_ID', + }, + logColumns: [], + fields: { + message: [], + }, + metricAlias: 'METRIC_ALIAS', + inventoryDefaultView: 'INVENTORY_DEFAULT_VIEW', + metricsExplorerDefaultView: 'METRICS_EXPLORER_DEFAULT_VIEW', + anomalyThreshold: 0, + }, +}; diff --git a/x-pack/plugins/infra/server/utils/map_source_to_log_view.ts b/x-pack/plugins/infra/server/utils/map_source_to_log_view.ts new file mode 100644 index 0000000000000..5dd5d021ccd6a --- /dev/null +++ b/x-pack/plugins/infra/server/utils/map_source_to_log_view.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 { LogIndexReference, LogView, LogViewAttributes } from '@kbn/logs-shared-plugin/common'; +import { LogIndexReference as SourceConfigurationLogIndexReference } from '../../common/source_configuration/source_configuration'; +import { InfraSource } from '../lib/sources'; + +export const mapSourceToLogView = (sourceConfiguration: InfraSource): LogView => { + return { + id: sourceConfiguration.id, + version: sourceConfiguration.version, + updatedAt: sourceConfiguration.updatedAt, + origin: `infra-source-${sourceConfiguration.origin}`, + attributes: getAttributesFromSourceConfiguration(sourceConfiguration), + }; +}; + +export const getAttributesFromSourceConfiguration = ({ + configuration: { name, description, logIndices, logColumns }, +}: InfraSource): LogViewAttributes => ({ + name, + description, + logIndices: getLogIndicesFromSourceConfigurationLogIndices(logIndices), + logColumns, +}); + +const getLogIndicesFromSourceConfigurationLogIndices = ( + logIndices: SourceConfigurationLogIndexReference +): LogIndexReference => + logIndices.type === 'index_pattern' + ? { + type: 'data_view', + dataViewId: logIndices.indexPatternId, + } + : logIndices; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index 997fbf24ea9c9..3f1ece70501de 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -38,7 +38,6 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/safer-lodash-set", "@kbn/test-jest-helpers", - "@kbn/test-subj-selector", "@kbn/controls-plugin", "@kbn/securitysolution-io-ts-types", "@kbn/config-schema", @@ -67,6 +66,7 @@ "@kbn/aiops-plugin", "@kbn/field-formats-plugin", "@kbn/core-http-server", + "@kbn/logs-shared-plugin", "@kbn/licensing-plugin", ], "exclude": ["target/**/*"] diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx new file mode 100644 index 0000000000000..0eb71d6f6e1d7 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.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, { useEffect, useState } from 'react'; +import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Provider } from 'react-redux'; +import { PreloadedState } from '@reduxjs/toolkit'; +import { css } from '@emotion/react'; +import type { CoreStart } from '@kbn/core/public'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import { + makeConfigureStore, + LensRootStore, + LensAppState, + LensState, +} from '../../../state_management'; +import { getPreloadedState } from '../../../state_management/lens_slice'; + +import type { DatasourceMap, VisualizationMap } from '../../../types'; +import { + LensEditConfigurationFlyout, + type EditConfigPanelProps, +} from './lens_configuration_flyout'; +import type { LensAppServices } from '../../types'; + +export type EditLensConfigurationProps = Omit< + EditConfigPanelProps, + 'startDependencies' | 'coreStart' | 'visualizationMap' | 'datasourceMap' +>; + +function LoadingSpinnerWithOverlay() { + return ( + + + + ); +} + +export function getEditLensConfiguration( + coreStart: CoreStart, + startDependencies: LensPluginStartDependencies, + visualizationMap?: VisualizationMap, + datasourceMap?: DatasourceMap +) { + return ({ + attributes, + dataView, + updateAll, + setIsFlyoutVisible, + datasourceId, + adaptersTables, + }: EditLensConfigurationProps) => { + const [lensServices, setLensServices] = useState(); + useEffect(() => { + async function loadLensService() { + const { getLensServices, getLensAttributeService } = await import( + '../../../async_services' + ); + const lensServicesT = await getLensServices( + coreStart, + startDependencies, + getLensAttributeService(coreStart, startDependencies) + ); + + setLensServices(lensServicesT); + } + loadLensService(); + }, []); + + if (!lensServices || !datasourceMap || !visualizationMap || !dataView.id) { + return ; + } + const datasourceState = attributes.state.datasourceStates[datasourceId]; + const storeDeps = { + lensServices, + datasourceMap, + visualizationMap, + initialContext: + datasourceState && 'initialContext' in datasourceState + ? datasourceState.initialContext + : undefined, + }; + const lensStore: LensRootStore = makeConfigureStore(storeDeps, { + lens: getPreloadedState(storeDeps) as LensAppState, + } as unknown as PreloadedState); + const closeFlyout = () => { + setIsFlyoutVisible?.(false); + }; + + const configPanelProps = { + attributes, + dataView, + updateAll, + setIsFlyoutVisible, + datasourceId, + adaptersTables, + coreStart, + startDependencies, + visualizationMap, + datasourceMap, + }; + + return ( + + + + + + ); + }; +} diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx new file mode 100644 index 0000000000000..dc05ff1577382 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx @@ -0,0 +1,433 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiFlyoutBody } from '@elastic/eui'; +import { mountWithProvider } from '../../../mocks'; +import type { Query, AggregateQuery } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { + mockVisualizationMap, + mockDatasourceMap, + mockStoreDeps, + mockDataPlugin, +} from '../../../mocks'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import { createMockStartDependencies } from '../../../editor_frame_service/mocks'; +import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { VisualizationToolbar } from '../../../editor_frame_service/editor_frame/workspace_panel'; +import { ConfigPanelWrapper } from '../../../editor_frame_service/editor_frame/config_panel/config_panel'; +import { + LensEditConfigurationFlyout, + type EditConfigPanelProps, +} from './lens_configuration_flyout'; + +let container: HTMLDivElement | undefined; + +beforeEach(() => { + container = document.createElement('div'); + container.id = 'lensContainer'; + document.body.appendChild(container); +}); + +afterEach(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + + container = undefined; +}); + +describe('LensEditConfigurationFlyout', () => { + const mockStartDependencies = + createMockStartDependencies() as unknown as LensPluginStartDependencies; + const data = mockDataPlugin(); + (data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({ + from: 'now-2m', + to: 'now', + }); + const startDependencies = { + ...mockStartDependencies, + data, + }; + + function prepareAndMountComponent( + props: ReturnType, + query?: Query | AggregateQuery + ) { + return mountWithProvider( + , + { + preloadedState: { + datasourceStates: { + testDatasource: { + isLoading: false, + state: 'state', + }, + }, + activeDatasourceId: 'testDatasource', + query: query as Query, + }, + storeDeps: mockStoreDeps({ + datasourceMap: props.datasourceMap, + visualizationMap: props.visualizationMap, + }), + }, + { + attachTo: container, + } + ); + } + + function getDefaultProps( + { datasourceMap = mockDatasourceMap(), visualizationMap = mockVisualizationMap() } = { + datasourceMap: mockDatasourceMap(), + visualizationMap: mockVisualizationMap(), + } + ) { + const lensAttributes = { + title: 'test', + visualizationType: 'testVis', + state: { + datasourceStates: { + testDatasource: {}, + }, + visualization: {}, + filters: [], + query: { + language: 'lucene', + query: '', + }, + }, + filters: [], + query: { + language: 'lucene', + query: '', + }, + references: [], + } as unknown as TypedLensByValueInput['attributes']; + + const dataView = { id: 'index1', isPersisted: () => true } as unknown as DataView; + return { + attributes: lensAttributes, + dataView, + updateAll: jest.fn(), + coreStart: coreMock.createStart(), + startDependencies, + visualizationMap, + datasourceMap, + setIsFlyoutVisible: jest.fn(), + datasourceId: 'testDatasource', + } as unknown as EditConfigPanelProps; + } + + it('should call the setIsFlyout callback if collapse button is clicked', async () => { + const setIsFlyoutVisibleSpy = jest.fn(); + const props = getDefaultProps(); + const newProps = { + ...props, + setIsFlyoutVisible: setIsFlyoutVisibleSpy, + }; + const { instance } = await prepareAndMountComponent(newProps); + expect(instance.find(EuiFlyoutBody).exists()).toBe(true); + instance.find('[data-test-subj="collapseFlyoutButton"]').at(1).simulate('click'); + expect(setIsFlyoutVisibleSpy).toHaveBeenCalled(); + }); + + it('should compute the frame public api correctly', async () => { + const props = getDefaultProps(); + const { instance } = await prepareAndMountComponent(props); + expect(instance.find(ConfigPanelWrapper).exists()).toBe(true); + expect(instance.find(VisualizationToolbar).exists()).toBe(true); + expect(instance.find(VisualizationToolbar).prop('framePublicAPI')).toMatchInlineSnapshot(` + Object { + "activeData": Object {}, + "dataViews": Object { + "indexPatternRefs": Array [], + "indexPatterns": Object { + "index1": Object { + "id": "index1", + "isPersisted": [Function], + }, + }, + }, + "datasourceLayers": Object { + "a": Object { + "datasourceId": "testDatasource", + "getFilters": [MockFunction], + "getMaxPossibleNumValues": [MockFunction], + "getOperationForColumnId": [MockFunction], + "getSourceId": [MockFunction], + "getTableSpec": [MockFunction], + "getVisualDefaults": [MockFunction], + "hasDefaultTimeField": [MockFunction], + "isTextBasedLanguage": [MockFunction] { + "calls": Array [ + Array [], + Array [], + ], + "results": Array [ + Object { + "type": "return", + "value": false, + }, + Object { + "type": "return", + "value": false, + }, + ], + }, + }, + }, + "dateRange": Object { + "fromDate": "2021-01-10T04:00:00.000Z", + "toDate": "2021-01-10T08:00:00.000Z", + }, + } + `); + }); + + it('should compute the activeVisualization correctly', async () => { + const props = getDefaultProps(); + const { instance } = await prepareAndMountComponent(props); + expect(instance.find(VisualizationToolbar).prop('activeVisualization')).toMatchInlineSnapshot(` + Object { + "appendLayer": [MockFunction], + "clearLayer": [MockFunction], + "getConfiguration": [MockFunction] { + "calls": Array [ + Array [ + Object { + "frame": Object { + "activeData": Object {}, + "dataViews": Object { + "indexPatternRefs": Array [], + "indexPatterns": Object { + "index1": Object { + "id": "index1", + "isPersisted": [Function], + }, + }, + }, + "datasourceLayers": Object { + "a": Object { + "datasourceId": "testDatasource", + "getFilters": [MockFunction], + "getMaxPossibleNumValues": [MockFunction], + "getOperationForColumnId": [MockFunction], + "getSourceId": [MockFunction], + "getTableSpec": [MockFunction], + "getVisualDefaults": [MockFunction], + "hasDefaultTimeField": [MockFunction], + "isTextBasedLanguage": [MockFunction] { + "calls": Array [ + Array [], + Array [], + ], + "results": Array [ + Object { + "type": "return", + "value": false, + }, + Object { + "type": "return", + "value": false, + }, + ], + }, + }, + }, + "dateRange": Object { + "fromDate": "2021-01-10T04:00:00.000Z", + "toDate": "2021-01-10T08:00:00.000Z", + }, + }, + "layerId": "layer1", + "state": Object {}, + }, + ], + Array [ + Object { + "frame": Object { + "activeData": Object {}, + "dataViews": Object { + "indexPatternRefs": Array [], + "indexPatterns": Object { + "index1": Object { + "id": "index1", + "isPersisted": [Function], + }, + }, + }, + "datasourceLayers": Object { + "a": Object { + "datasourceId": "testDatasource", + "getFilters": [MockFunction], + "getMaxPossibleNumValues": [MockFunction], + "getOperationForColumnId": [MockFunction], + "getSourceId": [MockFunction], + "getTableSpec": [MockFunction], + "getVisualDefaults": [MockFunction], + "hasDefaultTimeField": [MockFunction], + "isTextBasedLanguage": [MockFunction] { + "calls": Array [ + Array [], + Array [], + ], + "results": Array [ + Object { + "type": "return", + "value": false, + }, + Object { + "type": "return", + "value": false, + }, + ], + }, + }, + }, + "dateRange": Object { + "fromDate": "2021-01-10T04:00:00.000Z", + "toDate": "2021-01-10T08:00:00.000Z", + }, + }, + "layerId": "layer1", + "state": Object {}, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Object { + "groups": Array [ + Object { + "accessors": Array [], + "dataTestSubj": "mockVisA", + "filterOperations": [MockFunction], + "groupId": "a", + "groupLabel": "a", + "layerId": "layer1", + "supportsMoreColumns": true, + }, + ], + }, + }, + Object { + "type": "return", + "value": Object { + "groups": Array [ + Object { + "accessors": Array [], + "dataTestSubj": "mockVisA", + "filterOperations": [MockFunction], + "groupId": "a", + "groupLabel": "a", + "layerId": "layer1", + "supportsMoreColumns": true, + }, + ], + }, + }, + ], + }, + "getDescription": [MockFunction] { + "calls": Array [ + Array [ + Object {}, + ], + Array [ + Object {}, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Object { + "label": "", + }, + }, + Object { + "type": "return", + "value": Object { + "label": "", + }, + }, + ], + }, + "getLayerIds": [MockFunction] { + "calls": Array [ + Array [ + Object {}, + ], + Array [ + Object {}, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Array [ + "layer1", + ], + }, + Object { + "type": "return", + "value": Array [ + "layer1", + ], + }, + ], + }, + "getLayerType": [MockFunction] { + "calls": Array [ + Array [ + "layer1", + Object {}, + ], + Array [ + "layer1", + Object {}, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "data", + }, + Object { + "type": "return", + "value": "data", + }, + ], + }, + "getRenderEventCounters": [MockFunction], + "getSuggestions": [MockFunction], + "getSupportedLayers": [MockFunction], + "getVisualizationTypeId": [MockFunction], + "id": "testVis", + "initialize": [MockFunction], + "removeDimension": [MockFunction], + "removeLayer": [MockFunction], + "renderDimensionEditor": [MockFunction], + "setDimension": [MockFunction], + "switchVisualizationType": [MockFunction], + "toExpression": [MockFunction], + "toPreviewExpression": [MockFunction], + "visualizationTypes": Array [ + Object { + "groupLabel": "testVisGroup", + "icon": "empty", + "id": "testVis", + "label": "TEST", + }, + ], + } + `); + }); +}); diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx new file mode 100644 index 0000000000000..9fe486c58048a --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { + EuiButtonEmpty, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + useEuiTheme, + EuiCallOut, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import type { CoreStart } from '@kbn/core/public'; +import type { Datatable } from '@kbn/expressions-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { getResolvedDateRange } from '../../../utils'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import { + DataViewsState, + useLensDispatch, + updateStateFromSuggestion, +} from '../../../state_management'; +import { VisualizationToolbar } from '../../../editor_frame_service/editor_frame/workspace_panel'; + +import type { DatasourceMap, VisualizationMap, DatasourceLayers } from '../../../types'; +import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { ConfigPanelWrapper } from '../../../editor_frame_service/editor_frame/config_panel/config_panel'; + +export interface EditConfigPanelProps { + attributes: TypedLensByValueInput['attributes']; + dataView: DataView; + updateAll: (datasourceState: unknown, visualizationState: unknown) => void; + coreStart: CoreStart; + startDependencies: LensPluginStartDependencies; + visualizationMap: VisualizationMap; + datasourceMap: DatasourceMap; + setIsFlyoutVisible?: (flag: boolean) => void; + datasourceId: 'formBased' | 'textBased'; + adaptersTables?: Record; +} + +export function LensEditConfigurationFlyout({ + attributes, + dataView, + coreStart, + startDependencies, + visualizationMap, + datasourceMap, + datasourceId, + updateAll, + setIsFlyoutVisible, + adaptersTables, +}: EditConfigPanelProps) { + const currentDataViewId = dataView.id ?? ''; + const datasourceState = attributes.state.datasourceStates[datasourceId]; + const activeVisualization = visualizationMap[attributes.visualizationType]; + const activeDatasource = datasourceMap[datasourceId]; + const dispatchLens = useLensDispatch(); + const { euiTheme } = useEuiTheme(); + const dataViews = useMemo(() => { + return { + indexPatterns: { + [currentDataViewId]: dataView, + }, + indexPatternRefs: [], + } as unknown as DataViewsState; + }, [currentDataViewId, dataView]); + dispatchLens( + updateStateFromSuggestion({ + newDatasourceId: datasourceId, + visualizationId: activeVisualization.id, + visualizationState: attributes.state.visualization, + datasourceState, + dataViews, + }) + ); + + const datasourceLayers: DatasourceLayers = useMemo(() => { + return {}; + }, []); + const activeData: Record = useMemo(() => { + return {}; + }, []); + const layers = activeDatasource.getLayers(datasourceState); + layers.forEach((layer) => { + datasourceLayers[layer] = datasourceMap[datasourceId].getPublicAPI({ + state: datasourceState, + layerId: layer, + indexPatterns: dataViews.indexPatterns, + }); + if (adaptersTables) { + activeData[layer] = Object.values(adaptersTables)[0]; + } + }); + + const dateRange = getResolvedDateRange(startDependencies.data.query.timefilter.timefilter); + const framePublicAPI = useMemo(() => { + return { + activeData, + dataViews, + datasourceLayers, + dateRange, + }; + }, [activeData, dataViews, datasourceLayers, dateRange]); + + const closeFlyout = () => { + setIsFlyoutVisible?.(false); + }; + + const layerPanelsProps = { + framePublicAPI, + datasourceMap, + visualizationMap, + core: coreStart, + dataViews: startDependencies.dataViews, + uiActions: startDependencies.uiActions, + hideLayerHeader: true, + onUpdateStateCb: updateAll, + }; + return ( + <> + + + + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index 38a904c5617c9..d4c6fe5be8dcd 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -30,6 +30,7 @@ export * from './visualizations/gauge/gauge_visualization'; export * from './visualizations/gauge'; export * from './visualizations/tagcloud/tagcloud_visualization'; export * from './visualizations/tagcloud'; +export { getEditLensConfiguration } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; export * from './datasources/form_based/form_based'; export { getTextBasedDatasource } from './datasources/text_based/text_based_languages'; diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index 42b405c939d3c..d12505e93f07a 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -410,6 +410,22 @@ describe('Textbased Data Source', () => { ); expect(suggestions[0].state).toEqual({ ...state, + fieldList: [ + { + id: 'newid', + meta: { + type: 'number', + }, + name: 'bytes', + }, + { + id: 'newid', + meta: { + type: 'string', + }, + name: 'dest', + }, + ], layers: { newid: { allColumns: [ diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index 573137da1ebc2..653a3f30e1b44 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -122,6 +122,14 @@ export function getTextBasedDatasource({ const query = context.query; const updatedState = { ...state, + fieldList: + newColumns?.map((c) => { + return { + id: c.columnId, + name: c.fieldName, + meta: c.meta, + }; + }) ?? [], layers: { ...state.layers, [newLayerId]: { diff --git a/x-pack/plugins/lens/public/datasources/text_based/types.ts b/x-pack/plugins/lens/public/datasources/text_based/types.ts index 0594fdcf2fbc2..544996c904b77 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/types.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/types.ts @@ -31,12 +31,12 @@ export interface TextBasedLayer { export interface TextBasedPersistedState { layers: Record; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; } export type TextBasedPrivateState = TextBasedPersistedState & { indexPatternRefs: IndexPatternRef[]; fieldList: DatatableColumn[]; - initialContext?: VisualizeFieldContext | VisualizeEditorContext; }; export interface IndexPatternRef { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 78f7246c52e6d..f9e09143d3e43 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -170,13 +170,19 @@ describe('ConfigPanel', () => { it('allow datasources and visualizations to use setters', async () => { const props = getDefaultProps(); - const { instance, lensStore } = await prepareAndMountComponent(props); + const onUpdateCbSpy = jest.fn(); + const newProps = { + ...props, + onUpdateStateCb: onUpdateCbSpy, + }; + const { instance, lensStore } = await prepareAndMountComponent(newProps); const { updateDatasource, updateAll } = instance.find(LayerPanel).props(); const updater = () => 'updated'; updateDatasource('testDatasource', updater); await waitMs(0); expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + expect(onUpdateCbSpy).toHaveBeenCalled(); expect( (lensStore.dispatch as jest.Mock).mock.calls[0][0].payload.updater( props.datasourceStates.testDatasource.state @@ -184,6 +190,7 @@ describe('ConfigPanel', () => { ).toEqual('updated'); updateAll('testDatasource', updater, props.visualizationState); + expect(onUpdateCbSpy).toHaveBeenCalled(); // wait for one tick so async updater has a chance to trigger await waitMs(0); expect(lensStore.dispatch).toHaveBeenCalledTimes(2); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index af1549e00cc30..571fc5194d5e3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -6,6 +6,7 @@ */ import React, { useMemo, memo, useCallback } from 'react'; +import { useStore } from 'react-redux'; import { EuiForm } from '@elastic/eui'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { isOfAggregateQueryType } from '@kbn/es-query'; @@ -52,7 +53,8 @@ export function LayerPanels( activeVisualization: Visualization; } ) { - const { activeVisualization, datasourceMap, indexPatternService } = props; + const lensStore = useStore(); + const { activeVisualization, datasourceMap, indexPatternService, onUpdateStateCb } = props; const { activeDatasourceId, visualization, datasourceStates, query } = useLensSelector( (state) => state.lens ); @@ -74,8 +76,12 @@ export function LayerPanels( newState, }) ); + if (onUpdateStateCb && activeDatasourceId) { + const dsState = datasourceStates[activeDatasourceId].state; + onUpdateStateCb?.(dsState, newState); + } }, - [activeVisualization, dispatchLens] + [activeDatasourceId, activeVisualization.id, datasourceStates, dispatchLens, onUpdateStateCb] ); const updateDatasource = useMemo( () => @@ -90,9 +96,10 @@ export function LayerPanels( dontSyncLinkedDimensions, }) ); + onUpdateStateCb?.(newState, visualization.state); } }, - [dispatchLens] + [dispatchLens, onUpdateStateCb, visualization.state] ); const updateDatasourceAsync = useMemo( () => (datasourceId: string | undefined, newState: unknown) => { @@ -147,9 +154,10 @@ export function LayerPanels( }, }) ); + onUpdateStateCb?.(newDatasourceState, newVisualizationState); }, 0); }, - [dispatchLens] + [dispatchLens, onUpdateStateCb] ); const toggleFullscreen = useMemo( @@ -213,20 +221,21 @@ export function LayerPanels( visualizationId?: string; layerId?: string; }) => { - const indexPatterns = await props.indexPatternService.ensureIndexPattern({ + const indexPatterns = await props.indexPatternService?.ensureIndexPattern({ id: indexPatternId, cache: props.framePublicAPI.dataViews.indexPatterns, }); - - dispatchLens( - changeIndexPattern({ - indexPatternId, - datasourceIds: datasourceId ? [datasourceId] : [], - visualizationIds: visualizationId ? [visualizationId] : [], - layerId, - dataViews: { indexPatterns }, - }) - ); + if (indexPatterns) { + dispatchLens( + changeIndexPattern({ + indexPatternId, + datasourceIds: datasourceId ? [datasourceId] : [], + visualizationIds: visualizationId ? [visualizationId] : [], + layerId, + dataViews: { indexPatterns }, + }) + ); + } }, [dispatchLens, props.framePublicAPI.dataViews.indexPatterns, props.indexPatternService] ); @@ -262,6 +271,7 @@ export function LayerPanels( updateVisualization={setVisualizationState} updateDatasource={updateDatasource} updateDatasourceAsync={updateDatasourceAsync} + displayLayerSettings={!props.hideLayerHeader} onChangeIndexPattern={(args) => { onChangeIndexPattern(args); const layersToRemove = @@ -307,6 +317,13 @@ export function LayerPanels( const datasourcePublicAPI = props.framePublicAPI.datasourceLayers?.[layerId]; const datasourceId = datasourcePublicAPI?.datasourceId; dispatchLens(removeDimension({ ...dimensionProps, datasourceId })); + if (datasourceId && onUpdateStateCb) { + const newState = lensStore.getState().lens; + onUpdateStateCb( + newState.datasourceStates[datasourceId].state, + newState.visualization.state + ); + } }} toggleFullscreen={toggleFullscreen} indexPatternService={indexPatternService} @@ -336,19 +353,21 @@ export function LayerPanels( indexPatternId = dataView.id; } - const newIndexPatterns = await indexPatternService.ensureIndexPattern({ + const newIndexPatterns = await indexPatternService?.ensureIndexPattern({ id: indexPatternId, cache: props.framePublicAPI.dataViews.indexPatterns, }); - dispatchLens( - changeIndexPattern({ - dataViews: { indexPatterns: newIndexPatterns }, - datasourceIds: Object.keys(datasourceStates), - visualizationIds: visualization.activeId ? [visualization.activeId] : [], - indexPatternId, - }) - ); + if (newIndexPatterns) { + dispatchLens( + changeIndexPattern({ + dataViews: { indexPatterns: newIndexPatterns }, + datasourceIds: Object.keys(datasourceStates), + visualizationIds: visualization.activeId ? [visualization.activeId] : [], + indexPatternId, + }) + ); + } }, registerLibraryAnnotationGroup: (groupInfo) => dispatchLens(registerLibraryAnnotationGroup(groupInfo)), diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx index da300bb282cb2..58ec4bd9ff26a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx @@ -47,6 +47,7 @@ export const getSharedActions = ({ openLayerSettings, onCloneLayer, onRemoveLayer, + customRemoveModalText, }: { onRemoveLayer: () => void; onCloneLayer: () => void; @@ -54,12 +55,12 @@ export const getSharedActions = ({ layerId: string; isOnlyLayer: boolean; activeVisualization: Visualization; - visualizationState: unknown; layerType?: LayerType; isTextBasedLanguage?: boolean; hasLayerSettings: boolean; openLayerSettings: () => void; core: Pick; + customRemoveModalText?: { title?: string; description?: string }; }) => [ getOpenLayerSettingsAction({ hasLayerSettings, @@ -77,6 +78,7 @@ export const getSharedActions = ({ layerType, isOnlyLayer, core, + customModalText: customRemoveModalText, }), ]; @@ -189,7 +191,7 @@ export const LayerActions = (props: LayerActionsProps) => { ; + customModalText?: { title?: string; description?: string }; } const SKIP_DELETE_MODAL_KEY = 'skipDeleteModal'; const getCopy = ( layerType: LayerType, - isOnlyLayer?: boolean + isOnlyLayer?: boolean, + customModalText: { title?: string; description?: string } | undefined = undefined ): { buttonLabel: string; modalTitle: string; modalBody: string } => { if (isOnlyLayer && layerType === 'data') { return { @@ -64,34 +66,46 @@ const getCopy = ( case 'data': return { buttonLabel, - modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteVis', { - defaultMessage: 'Delete visualization layer?', - }), - modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteVis', { - defaultMessage: `Deleting this layer removes the visualization and its configurations. `, - }), + modalTitle: + customModalText?.title ?? + i18n.translate('xpack.lens.modalTitle.title.deleteVis', { + defaultMessage: 'Delete visualization layer?', + }), + modalBody: + customModalText?.description ?? + i18n.translate('xpack.lens.layer.confirmModal.deleteVis', { + defaultMessage: `Deleting this layer removes the visualization and its configurations. `, + }), }; case 'annotations': return { buttonLabel, - modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteAnnotations', { - defaultMessage: 'Delete annotations layer?', - }), - modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteAnnotation', { - defaultMessage: `Deleting this layer removes the annotations and their configurations. `, - }), + modalTitle: + customModalText?.title ?? + i18n.translate('xpack.lens.modalTitle.title.deleteAnnotations', { + defaultMessage: 'Delete annotation group?', + }), + modalBody: + customModalText?.description ?? + i18n.translate('xpack.lens.layer.confirmModal.deleteAnnotation', { + defaultMessage: `Deleting this layer removes the annotations and their configurations. `, + }), }; case 'referenceLine': return { buttonLabel, - modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteReferenceLines', { - defaultMessage: 'Delete reference lines layer?', - }), - modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteRefLine', { - defaultMessage: `Deleting this layer removes the reference lines and their configurations. `, - }), + modalTitle: + customModalText?.title ?? + i18n.translate('xpack.lens.modalTitle.title.deleteReferenceLines', { + defaultMessage: 'Delete reference lines layer?', + }), + modalBody: + customModalText?.description ?? + i18n.translate('xpack.lens.layer.confirmModal.deleteRefLine', { + defaultMessage: `Deleting this layer removes the reference lines and their configurations. `, + }), }; default: @@ -193,7 +207,8 @@ const RemoveConfirmModal = ({ export const getRemoveLayerAction = (props: RemoveLayerAction): LayerAction => { const { buttonLabel, modalTitle, modalBody } = getCopy( props.layerType || LayerTypes.DATA, - props.isOnlyLayer + props.isOnlyLayer, + props.customModalText ); return { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 248681717b082..2b8568bd12908 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -11,6 +11,7 @@ import { EuiFormRow } from '@elastic/eui'; import { ChildDragDropProvider, DragDrop } from '@kbn/dom-drag-drop'; import { FramePublicAPI, Visualization, VisualizationConfigProps } from '../../../types'; import { LayerPanel } from './layer_panel'; +import { LayerActions } from './layer_actions'; import { coreMock } from '@kbn/core/public/mocks'; import { generateId } from '../../../id_generator'; import { @@ -116,6 +117,7 @@ describe('LayerPanel', () => { onChangeIndexPattern: jest.fn(), indexPatternService: createIndexPatternServiceMock(), getUserMessages: () => [], + displayLayerSettings: true, }; } @@ -203,6 +205,13 @@ describe('LayerPanel', () => { expect(optionalLabel.text()).toEqual('Optional'); }); + it('should hide the layer actions if displayLayerSettings is set to false', async () => { + const { instance } = await mountWithProvider( + + ); + expect(instance.find(LayerActions).exists()).toBe(false); + }); + it('should render the group with a way to add a new column', async () => { mockVisualization.getConfiguration.mockReturnValue({ groups: [ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index bb90c82b235e6..4a69695df3489 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -87,8 +87,9 @@ export function LayerPanel( datasourceId?: string; visualizationId?: string; }) => void; - indexPatternService: IndexPatternServiceAPI; - getUserMessages: UserMessagesGetter; + indexPatternService?: IndexPatternServiceAPI; + getUserMessages?: UserMessagesGetter; + displayLayerSettings: boolean; } ) { const [activeDimension, setActiveDimension] = useState( @@ -363,7 +364,6 @@ export function LayerPanel( ...getSharedActions({ layerId, activeVisualization, - visualizationState, core, layerIndex, layerType: activeVisualization.getLayerType(layerId, visualizationState), @@ -377,6 +377,10 @@ export function LayerPanel( openLayerSettings: () => setPanelSettingsOpen(true), onCloneLayer, onRemoveLayer: () => onRemoveLayer(layerId), + customRemoveModalText: activeVisualization.getCustomRemoveLayerText?.( + layerId, + visualizationState + ), }), ].filter((i) => i.isCompatible), [ @@ -418,17 +422,20 @@ export function LayerPanel( activeVisualization={activeVisualization} /> - - -
- + {props.displayLayerSettings && ( + + +
+ + )} - {(layerDatasource || activeVisualization.renderLayerPanel) && } - {layerDatasource && ( + {props.indexPatternService && + (layerDatasource || activeVisualization.renderLayerPanel) && } + {layerDatasource && props.indexPatternService && ( { const { columnId } = accessorConfig; - const messages = props.getUserMessages('dimensionButton', { - dimensionId: columnId, - }); + const messages = + props?.getUserMessages?.('dimensionButton', { + dimensionId: columnId, + }) ?? []; return ( void; } export interface LayerPanelProps { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/index.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/index.ts index c3ba019ca68ad..9f51ea611e9b8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/index.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/index.ts @@ -6,3 +6,4 @@ */ export { WorkspacePanel } from './workspace_panel'; +export { VisualizationToolbar } from './workspace_panel_wrapper'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx index 42735bde405c4..700fe7f96bf84 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx @@ -65,7 +65,13 @@ describe('workspace_panel_wrapper', () => { isFullscreen={false} lensInspector={{} as unknown as LensInspector} getUserMessages={() => []} - /> + />, + { + preloadedState: { + visualization: { activeId: 'myVis', state: visState }, + datasourceStates: {}, + }, + } ); expect(renderToolbarMock).toHaveBeenCalledWith(expect.any(Element), { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx index 6b61e4dd374c5..064b268209aea 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx @@ -16,6 +16,7 @@ import { FramePublicAPI, UserMessagesGetter, VisualizationMap, + Visualization, } from '../../../types'; import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; import { NativeRenderer } from '../../../native_renderer'; @@ -49,6 +50,52 @@ export interface WorkspacePanelWrapperProps { getUserMessages: UserMessagesGetter; } +export function VisualizationToolbar(props: { + activeVisualization: Visualization | null; + framePublicAPI: FramePublicAPI; + onUpdateStateCb?: (datasourceState: unknown, visualizationState: unknown) => void; +}) { + const dispatchLens = useLensDispatch(); + const { activeDatasourceId, visualization, datasourceStates } = useLensSelector( + (state) => state.lens + ); + const setVisualizationState = useCallback( + (newState: unknown) => { + if (!props.activeVisualization) { + return; + } + dispatchLens( + updateVisualizationState({ + visualizationId: props.activeVisualization.id, + newState, + }) + ); + if (activeDatasourceId && props.onUpdateStateCb) { + const dsState = datasourceStates[activeDatasourceId].state; + props.onUpdateStateCb?.(dsState, newState); + } + }, + [activeDatasourceId, datasourceStates, dispatchLens, props] + ); + + return ( + <> + {props.activeVisualization && props.activeVisualization.renderToolbar && ( + + + + )} + + ); +} + export function WorkspacePanelWrapper({ children, framePublicAPI, @@ -65,21 +112,6 @@ export function WorkspacePanelWrapper({ const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled); const activeVisualization = visualizationId ? visualizationMap[visualizationId] : null; - const setVisualizationState = useCallback( - (newState: unknown) => { - if (!activeVisualization) { - return; - } - dispatchLens( - updateVisualizationState({ - visualizationId: activeVisualization.id, - newState, - }) - ); - }, - [dispatchLens, activeVisualization] - ); - const userMessages = getUserMessages('toolbar'); return ( @@ -116,19 +148,10 @@ export function WorkspacePanelWrapper({ framePublicAPI={framePublicAPI} /> - - {activeVisualization && activeVisualization.renderToolbar && ( - - - - )} + )} diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index edccc071a57d1..4513e0f4bffd4 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -10,6 +10,8 @@ import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/publ import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { EditorFrameSetupPlugins, EditorFrameStartPlugins } from './service'; @@ -57,5 +59,7 @@ export function createMockStartDependencies() { embeddable: embeddablePluginMock.createStartContract(), expressions: expressionsPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), + dataViews: dataViewPluginMocks.createStartContract(), } as unknown as MockedStartDependencies; } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index 943e87c9c00c2..67076fb9c9200 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -23,6 +23,7 @@ import { import type { LensByReferenceInput, LensByValueInput } from './embeddable'; import type { Document } from '../persistence'; import type { FormBasedPersistedState } from '../datasources/form_based/types'; +import type { TextBasedPersistedState } from '../datasources/text_based/types'; import type { XYState } from '../visualizations/xy/types'; import type { PieVisualizationState, @@ -45,6 +46,7 @@ type LensAttributes = Omit< state: Omit & { datasourceStates: { formBased: FormBasedPersistedState; + textBased?: TextBasedPersistedState; }; visualization: TVisState; }; diff --git a/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx index cbf310cb2f50a..f526e46d8f5ec 100644 --- a/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx @@ -21,6 +21,7 @@ export const lensPluginMock = { SaveModalComponent: jest.fn(() => { return Lens Save Modal Component; }), + EditLensConfigPanelApi: jest.fn().mockResolvedValue(Lens Config Panel Component), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), getXyVisTypes: jest diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index b207f07266ae8..1a195183142c3 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -126,6 +126,7 @@ import { type LensAppLocator, LensAppLocatorDefinition } from '../common/locator import { downloadCsvShareProvider } from './app_plugin/csv_download_provider/csv_download_provider'; import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; +import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; export interface LensPluginSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -215,6 +216,14 @@ export interface LensPublicStart { * @experimental */ SaveModalComponent: React.ComponentType>; + /** + * React component which can be used to embed a Lens Visualization Config Panel Component. + * + * This API might undergo breaking changes even in minor versions. + * + * @experimental + */ + EditLensConfigPanelApi: () => Promise; /** * Method which navigates to the Lens editor, loading the state specified by the `input` parameter. * See `x-pack/examples/embedded_lens_example` for exemplary usage. @@ -252,6 +261,8 @@ export interface LensPublicStart { }>; } +export type EditLensConfigPanelComponent = React.ComponentType; + export type LensSuggestionsApi = ( context: VisualizeFieldContext | VisualizeEditorContext, dataViews: DataView, @@ -649,6 +660,17 @@ export class LensPlugin { }, }; }, + EditLensConfigPanelApi: async () => { + const { getEditLensConfiguration } = await import('./async_services'); + if (!this.editorFrameService) { + this.initDependenciesForApi(); + } + const [visualizationMap, datasourceMap] = await Promise.all([ + this.editorFrameService!.loadVisualizations(), + this.editorFrameService!.loadDatasources(), + ]); + return getEditLensConfiguration(core, startDependencies, visualizationMap, datasourceMap); + }, }; } diff --git a/x-pack/plugins/lens/public/shared_components/static_header.tsx b/x-pack/plugins/lens/public/shared_components/static_header.tsx index 8069a2d2c9849..b4c5d8931065e 100644 --- a/x-pack/plugins/lens/public/shared_components/static_header.tsx +++ b/x-pack/plugins/lens/public/shared_components/static_header.tsx @@ -40,15 +40,7 @@ export const StaticHeader = ({
{label}
- {indicator && ( -
- {indicator} -
- )} + {indicator} ); diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index 9a9a4005714aa..f4b333e25c815 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -35,6 +35,7 @@ export const { submitSuggestion, switchDatasource, switchAndCleanDatasource, + updateStateFromSuggestion, updateIndexPatterns, setToggleFullscreen, initEmpty, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index c69931837b3aa..0371d5564d503 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -10,6 +10,7 @@ import type { Query } from '@kbn/es-query'; import { switchDatasource, switchAndCleanDatasource, + updateStateFromSuggestion, switchVisualization, setState, updateState, @@ -271,6 +272,28 @@ describe('lensSlice', () => { }); }); + describe('update the state from the suggestion', () => { + it('should switch active datasource and initialize new state', () => { + store.dispatch( + updateStateFromSuggestion({ + newDatasourceId: 'testDatasource2', + visualizationId: 'testVis', + visualizationState: ['col1', 'col2'], + datasourceState: {}, + dataViews: { indexPatterns: {} } as DataViewsState, + }) + ); + expect(store.getState().lens.activeDatasourceId).toEqual('testDatasource2'); + expect(store.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual(false); + expect(store.getState().lens.datasourceStates.testDatasource2.state).toStrictEqual({}); + expect(store.getState().lens.visualization).toStrictEqual({ + activeId: 'testVis', + state: ['col1', 'col2'], + }); + expect(store.getState().lens.dataViews).toEqual({ indexPatterns: {} }); + }); + }); + describe('adding or removing layer', () => { const testDatasource = (datasourceId: string) => { return { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index e7829361ca5f9..cb35f16f0e8c5 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -173,6 +173,13 @@ export const switchAndCleanDatasource = createAction<{ visualizationId: string | null; currentIndexPatternId?: string; }>('lens/switchAndCleanDatasource'); +export const updateStateFromSuggestion = createAction<{ + newDatasourceId: string; + visualizationId: string | null; + visualizationState: unknown; + datasourceState: unknown; + dataViews: DataViewsState; +}>('lens/updateStateFromSuggestion'); export const navigateAway = createAction('lens/navigateAway'); export const loadInitial = createAction<{ initialInput?: LensEmbeddableInput; @@ -267,6 +274,7 @@ export const lensActions = { submitSuggestion, switchDatasource, switchAndCleanDatasource, + updateStateFromSuggestion, navigateAway, loadInitial, initEmpty, @@ -848,6 +856,42 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }, }; }, + [updateStateFromSuggestion.type]: ( + state, + { + payload, + }: { + payload: { + newDatasourceId: string; + visualizationId: string; + visualizationState: unknown; + datasourceState: unknown; + dataViews: DataViewsState; + }; + } + ) => { + const visualization = { + activeId: payload.visualizationId, + state: payload.visualizationState, + }; + + const datasourceState = payload.datasourceState; + + return { + ...state, + datasourceStates: { + [payload.newDatasourceId]: { + state: datasourceState, + isLoading: false, + }, + }, + activeDatasourceId: payload.newDatasourceId, + visualization: { + ...visualization, + }, + dataViews: payload.dataViews, + }; + }, [navigateAway.type]: (state) => state, [loadInitial.type]: ( state, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 005c016d0580e..99cf847508133 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -1118,6 +1118,15 @@ export interface Visualization LayerAction[]; + /** + * This method is a clunky solution to the problem, but I'm banking on the confirm modal being removed + * with undo/redo anyways + */ + getCustomRemoveLayerText?: ( + layerId: string, + state: T + ) => { title?: string; description?: string } | undefined; + /** returns the type string of the given layer */ getLayerType: (layerId: string, state?: T) => LayerType | undefined; diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx index 8505f9811749a..cf389614a50b4 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx @@ -113,7 +113,7 @@ describe('annotation group save action', () => { describe('save routine', () => { const layerId = 'mylayerid'; - const layer: XYByValueAnnotationLayerConfig = { + const byValueLayer: XYByValueAnnotationLayerConfig = { layerId, layerType: 'annotations', indexPatternId: 'some-index-pattern', @@ -144,10 +144,11 @@ describe('annotation group save action', () => { legend: { isVisible: true, position: 'bottom' }, layers: [{ layerId } as XYAnnotationLayerConfig], } as XYState, - layer, + layer: byValueLayer, setState: jest.fn(), eventAnnotationService: { createAnnotationGroup: jest.fn(() => Promise.resolve({ id: savedId })), + groupExistsWithTitle: jest.fn(() => Promise.resolve(false)), updateAnnotationGroup: jest.fn(), loadAnnotationGroup: jest.fn(), toExpression: jest.fn(), @@ -162,7 +163,7 @@ describe('annotation group save action', () => { newTags: ['my-tag'], newCopyOnSave: false, isTitleDuplicateConfirmed: false, - onTitleDuplicate: () => {}, + onTitleDuplicate: jest.fn(), }, dataViews, goToAnnotationLibrary: () => Promise.resolve(), @@ -321,5 +322,78 @@ describe('annotation group save action', () => { expect(props.toasts.addSuccess).toHaveBeenCalledTimes(1); }); + + it.each` + existingGroup | newCopyOnSave | titleChanged | isTitleDuplicateConfirmed | expectPreventSave + ${false} | ${false} | ${false} | ${false} | ${true} + ${false} | ${false} | ${false} | ${true} | ${false} + ${true} | ${false} | ${false} | ${false} | ${false} + ${true} | ${true} | ${false} | ${false} | ${true} + ${true} | ${true} | ${false} | ${true} | ${false} + `( + 'checks duplicate title when saving group', + async ({ + existingGroup, + newCopyOnSave, + titleChanged, + isTitleDuplicateConfirmed, + expectPreventSave, + }) => { + (props.eventAnnotationService.groupExistsWithTitle as jest.Mock).mockResolvedValueOnce( + true + ); + + const oldTitle = 'old title'; + let layer: XYAnnotationLayerConfig = byValueLayer; + if (existingGroup) { + const byReferenceLayer: XYByReferenceAnnotationLayerConfig = { + ...props.layer, + annotationGroupId: 'my-group-id', + __lastSaved: { + ...props.layer, + title: oldTitle, + description: 'description', + tags: [], + }, + }; + layer = byReferenceLayer; + } + + const newTitle = titleChanged ? 'my changed title' : oldTitle; + + await onSave({ + ...props, + layer, + modalOnSaveProps: { + ...props.modalOnSaveProps, + newTitle, + isTitleDuplicateConfirmed, + newCopyOnSave, + }, + }); + + if (expectPreventSave) { + expect(props.eventAnnotationService.updateAnnotationGroup).not.toHaveBeenCalled(); + + expect(props.eventAnnotationService.createAnnotationGroup).not.toHaveBeenCalled(); + + expect(props.modalOnSaveProps.closeModal).not.toHaveBeenCalled(); + + expect(props.setState).not.toHaveBeenCalled(); + + expect(props.toasts.addSuccess).not.toHaveBeenCalled(); + + expect(props.modalOnSaveProps.onTitleDuplicate).toHaveBeenCalled(); + } else { + expect(props.modalOnSaveProps.onTitleDuplicate).not.toHaveBeenCalled(); + + expect(props.modalOnSaveProps.closeModal).toHaveBeenCalled(); + + expect(props.setState).toHaveBeenCalled(); + + expect(props.toasts.addSuccess).toHaveBeenCalledTimes(1); + } + } + ); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx index d0888189b8592..803dc3e41e87d 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx @@ -133,6 +133,28 @@ const saveAnnotationGroupToLibrary = async ( return { id: savedId, config: groupConfig }; }; +const shouldStopBecauseDuplicateTitle = async ( + newTitle: string, + existingTitle: string, + newCopyOnSave: ModalOnSaveProps['newCopyOnSave'], + onTitleDuplicate: ModalOnSaveProps['onTitleDuplicate'], + isTitleDuplicateConfirmed: ModalOnSaveProps['isTitleDuplicateConfirmed'], + eventAnnotationService: EventAnnotationServiceType +) => { + if (isTitleDuplicateConfirmed || (newTitle === existingTitle && !newCopyOnSave)) { + return false; + } + + const duplicateExists = await eventAnnotationService.groupExistsWithTitle(newTitle); + + if (duplicateExists) { + onTitleDuplicate(); + return true; + } else { + return false; + } +}; + /** @internal exported for testing only */ export const onSave = async ({ state, @@ -140,7 +162,15 @@ export const onSave = async ({ setState, eventAnnotationService, toasts, - modalOnSaveProps: { newTitle, newDescription, newTags, closeModal, newCopyOnSave }, + modalOnSaveProps: { + newTitle, + newDescription, + newTags, + closeModal, + newCopyOnSave, + onTitleDuplicate, + isTitleDuplicateConfirmed, + }, dataViews, goToAnnotationLibrary, }: { @@ -153,6 +183,17 @@ export const onSave = async ({ dataViews: DataViewsContract; goToAnnotationLibrary: () => Promise; }) => { + const shouldStop = await shouldStopBecauseDuplicateTitle( + newTitle, + isByReferenceAnnotationsLayer(layer) ? layer.__lastSaved.title : '', + newCopyOnSave, + onTitleDuplicate, + isTitleDuplicateConfirmed, + eventAnnotationService + ); + + if (shouldStop) return; + let savedInfo: Awaited>; try { savedInfo = await saveAnnotationGroupToLibrary( @@ -205,27 +246,25 @@ export const onSave = async ({ text: ((element) => render( -

- goToAnnotationLibrary()} - > - {i18n.translate( - 'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.annotationLibrary', - { - defaultMessage: 'annotation library', - } - )} - - ), - }} - /> -

+ goToAnnotationLibrary()} + > + {i18n.translate( + 'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.annotationLibrary', + { + defaultMessage: 'annotation library', + } + )} + + ), + }} + />
, element )) as MountPoint, @@ -258,7 +297,7 @@ export const getSaveLayerAction = ({ const displayName = i18n.translate( 'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary', { - defaultMessage: 'Save annotation group', + defaultMessage: 'Save to library', } ); diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx index ec89021686c6d..b9b29a981c2da 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx @@ -416,16 +416,6 @@ export const getAnnotationsConfiguration = ({ }) => { const groupLabel = getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }); - const emptyButtonLabels = { - buttonAriaLabel: i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', { - defaultMessage: 'Add an annotation to {groupLabel}', - values: { groupLabel }, - }), - buttonLabel: i18n.translate('xpack.lens.configure.emptyConfigClick', { - defaultMessage: 'Add an annotation', - }), - }; - return { groups: [ { @@ -445,7 +435,6 @@ export const getAnnotationsConfiguration = ({ supportFieldFormat: false, enableDimensionEditor: true, filterOperations: () => false, - labels: emptyButtonLabels, }, ], }; diff --git a/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx b/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx index ba22b00ccd362..cb9521b42e0a6 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx @@ -70,6 +70,7 @@ export function LoadAnnotationLibraryFlyout({
{ }); }); + describe('#cloneLayer', () => { + it('should turned cloned by-reference annotation groups into by-value', () => { + const state = exampleState(); + const layer: XYByValueAnnotationLayerConfig = { + layerId: 'layer-id', + layerType: 'annotations', + indexPatternId: 'some-index-pattern', + ignoreGlobalFilters: false, + annotations: [ + { + id: 'some-annotation-id', + type: 'manual', + key: { + type: 'point_in_time', + timestamp: 'timestamp', + }, + } as PointInTimeEventAnnotationConfig, + ], + }; + + state.layers = [ + { + ...layer, + annotationGroupId: 'some-group-id', + __lastSaved: { + ...layer, + title: '', + description: '', + tags: [], + }, + }, + ]; + + const newLayerId = 'new-layer-id'; + + const stateWithClonedLayer = xyVisualization.cloneLayer!( + state, + layer.layerId, + newLayerId, + new Map() + ); + + expect( + isAnnotationsLayer(stateWithClonedLayer.layers[0]) && + isByReferenceAnnotationsLayer(stateWithClonedLayer.layers[0]) + ).toBe(true); + expect( + isAnnotationsLayer(stateWithClonedLayer.layers[1]) && + isByReferenceAnnotationsLayer(stateWithClonedLayer.layers[1]) + ).toBe(false); + }); + }); + describe('#getUniqueLabels', () => { it('creates unique labels for single annotations layer with repeating labels', async () => { const annotationLayer: XYAnnotationLayerConfig = { @@ -3638,7 +3695,7 @@ describe('xy_visualization', () => { Object { "data-test-subj": "lnsXY_annotationLayer_saveToLibrary", "description": "Saves annotation group as separate saved object", - "displayName": "Save annotation group", + "displayName": "Save to library", "execute": [Function], "icon": "save", "isCompatible": true, @@ -3899,4 +3956,52 @@ describe('xy_visualization', () => { ).not.toThrowError(); }); }); + + describe('#getCustomRemoveLayerText', () => { + it('should NOT return custom text for the remove layer button if not by-reference', () => { + expect(xyVisualization.getCustomRemoveLayerText!('first', exampleState())).toBeUndefined(); + }); + + it('should return custom text for the remove layer button if by-reference', () => { + const layerId = 'layer-id'; + + const commonProps = { + layerId, + layerType: 'annotations' as const, + indexPatternId: 'some-index-pattern', + ignoreGlobalFilters: false, + annotations: [ + { + id: 'some-annotation-id', + type: 'manual', + key: { + type: 'point_in_time', + timestamp: 'timestamp', + }, + } as PointInTimeEventAnnotationConfig, + ], + }; + + const layer: XYByReferenceAnnotationLayerConfig = { + ...commonProps, + annotationGroupId: 'some-group-id', + __lastSaved: { + ...commonProps, + title: 'My saved object title', + description: 'some description', + tags: [], + }, + }; + expect( + xyVisualization.getCustomRemoveLayerText!(layerId, { + ...exampleState(), + layers: [layer], + }) + ).toMatchInlineSnapshot(` + Object { + "title": "Delete \\"My saved object title\\"", + } + `); + }); + }); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 1a3d002d3baea..f74bcc9145b06 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -97,6 +97,7 @@ import { getVisualizationType, isAnnotationsLayer, isBucketed, + isByReferenceAnnotationsLayer, isDataLayer, isNumericDynamicMetric, isReferenceLayer, @@ -105,7 +106,7 @@ import { validateLayersForDimension, } from './visualization_helpers'; import { groupAxesByType } from './axes_configuration'; -import type { XYState } from './types'; +import type { XYByValueAnnotationLayerConfig, XYState } from './types'; import { ReferenceLinePanel } from './xy_config_panel/reference_line_config_panel'; import { AnnotationsPanel } from './xy_config_panel/annotations_config_panel'; import { defaultAnnotationLabel } from './annotations/helpers'; @@ -173,10 +174,25 @@ export const getXyVisualization = ({ if (isAnnotationsLayer(toCopyLayer)) { toCopyLayer.annotations.forEach((i) => clonedIDsMap.set(i.id, generateId())); } - const newLayer = renewIDs(toCopyLayer, [...clonedIDsMap.keys()], (id: string) => + + let newLayer = renewIDs(toCopyLayer, [...clonedIDsMap.keys()], (id: string) => clonedIDsMap.get(id) ); + newLayer.layerId = newLayerId; + + if (isAnnotationsLayer(newLayer) && isByReferenceAnnotationsLayer(newLayer)) { + const byValueVersion: XYByValueAnnotationLayerConfig = { + annotations: newLayer.annotations, + ignoreGlobalFilters: newLayer.ignoreGlobalFilters, + layerId: newLayer.layerId, + layerType: newLayer.layerType, + indexPatternId: newLayer.indexPatternId, + }; + + newLayer = byValueVersion; + } + return { ...state, layers: [...state.layers, newLayer], @@ -304,6 +320,14 @@ export const getXyVisualization = ({ return actions; }, + getCustomRemoveLayerText(layerId, state) { + const layerIndex = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[layerIndex]; + if (layer && isByReferenceAnnotationsLayer(layer)) { + return { title: `Delete "${layer.__lastSaved.title}"` }; + } + }, + hasLayerSettings({ state, layerId: currentLayerId }) { const layer = state.layers?.find(({ layerId }) => layerId === currentLayerId); return { data: Boolean(layer && isAnnotationsLayer(layer)), appearance: false }; diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx index a549ac42d9448..c9bd442301596 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx @@ -161,7 +161,7 @@ export const isPersistedByValueAnnotationsLayer = ( (layer.persistanceType === 'byValue' || !layer.persistanceType); export const isByReferenceAnnotationsLayer = ( - layer: XYAnnotationLayerConfig + layer: XYLayerConfig ): layer is XYByReferenceAnnotationLayerConfig => 'annotationGroupId' in layer && '__lastSaved' in layer; diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx index eab7da089e07d..cb37fcbf5edfc 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx @@ -19,6 +19,7 @@ import { import { ToolbarButton } from '@kbn/kibana-react-plugin/public'; import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons'; import { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; import { getIgnoreGlobalFilterIcon } from '../../../shared_components/ignore_global_filter/data_view_picker_icon'; import type { VisualizationLayerHeaderContentProps, @@ -96,13 +97,20 @@ function AnnotationsLayerHeader({ } indicator={ hasUnsavedChanges && ( - +
+ +
) } /> diff --git a/x-pack/plugins/logs_shared/README.md b/x-pack/plugins/logs_shared/README.md new file mode 100755 index 0000000000000..16483a988ec4a --- /dev/null +++ b/x-pack/plugins/logs_shared/README.md @@ -0,0 +1,3 @@ +# Logs Shared + +Exposes the shared components and APIs to access and visualize logs. diff --git a/x-pack/plugins/logs_shared/common/constants.ts b/x-pack/plugins/logs_shared/common/constants.ts new file mode 100644 index 0000000000000..7e49b14458a9e --- /dev/null +++ b/x-pack/plugins/logs_shared/common/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const TIMESTAMP_FIELD = '@timestamp'; +export const MESSAGE_FIELD = 'message'; +export const TIEBREAKER_FIELD = '_doc'; diff --git a/x-pack/plugins/logs_shared/common/dynamic.tsx b/x-pack/plugins/logs_shared/common/dynamic.tsx new file mode 100644 index 0000000000000..a66dbaa10b5aa --- /dev/null +++ b/x-pack/plugins/logs_shared/common/dynamic.tsx @@ -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 React, { lazy, Suspense } from 'react'; + +type LoadableComponent = () => any; + +interface DynamicOptions { + fallback?: React.ReactNode; +} + +/** + * Lazy load and wrap with Suspense any component. + * + * @example + * const Header = dynamic(() => import('./components/header')) + */ +export function dynamic(loader: LoadableComponent, options: DynamicOptions = {}) { + const Component = lazy(loader); + + return (props: any) => ( + + + + ); +} diff --git a/x-pack/plugins/logs_shared/common/formatters/datetime.ts b/x-pack/plugins/logs_shared/common/formatters/datetime.ts new file mode 100644 index 0000000000000..270dd7aa73808 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/formatters/datetime.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 { i18n } from '@kbn/i18n'; + +export function localizedDate(dateTime: number | Date, locale: string = i18n.getLocale()) { + const formatter = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + + return formatter.format(dateTime); +} diff --git a/x-pack/plugins/logs_shared/common/http_api/index.ts b/x-pack/plugins/logs_shared/common/http_api/index.ts new file mode 100644 index 0000000000000..939f72786183b --- /dev/null +++ b/x-pack/plugins/logs_shared/common/http_api/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Exporting versioned APIs types + */ +export * from './latest'; +export * as logEntriesV1 from './log_entries/v1'; +export * as logViewsV1 from './log_views/v1'; diff --git a/x-pack/plugins/logs_shared/common/http_api/latest.ts b/x-pack/plugins/logs_shared/common/http_api/latest.ts new file mode 100644 index 0000000000000..63f58bf4f92c0 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/http_api/latest.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './log_entries/v1'; +export * from './log_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/log_entries/v1/highlights.ts b/x-pack/plugins/logs_shared/common/http_api/log_entries/v1/highlights.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_entries/v1/highlights.ts rename to x-pack/plugins/logs_shared/common/http_api/log_entries/v1/highlights.ts diff --git a/x-pack/plugins/infra/common/http_api/log_entries/v1/index.ts b/x-pack/plugins/logs_shared/common/http_api/log_entries/v1/index.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_entries/v1/index.ts rename to x-pack/plugins/logs_shared/common/http_api/log_entries/v1/index.ts diff --git a/x-pack/plugins/infra/common/http_api/log_entries/v1/summary.ts b/x-pack/plugins/logs_shared/common/http_api/log_entries/v1/summary.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_entries/v1/summary.ts rename to x-pack/plugins/logs_shared/common/http_api/log_entries/v1/summary.ts diff --git a/x-pack/plugins/infra/common/http_api/log_entries/v1/summary_highlights.ts b/x-pack/plugins/logs_shared/common/http_api/log_entries/v1/summary_highlights.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_entries/v1/summary_highlights.ts rename to x-pack/plugins/logs_shared/common/http_api/log_entries/v1/summary_highlights.ts diff --git a/x-pack/plugins/infra/common/http_api/log_views/common.ts b/x-pack/plugins/logs_shared/common/http_api/log_views/common.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_views/common.ts rename to x-pack/plugins/logs_shared/common/http_api/log_views/common.ts diff --git a/x-pack/plugins/infra/common/http_api/log_views/index.ts b/x-pack/plugins/logs_shared/common/http_api/log_views/index.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_views/index.ts rename to x-pack/plugins/logs_shared/common/http_api/log_views/index.ts diff --git a/x-pack/plugins/infra/common/http_api/log_views/v1/get_log_view.ts b/x-pack/plugins/logs_shared/common/http_api/log_views/v1/get_log_view.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_views/v1/get_log_view.ts rename to x-pack/plugins/logs_shared/common/http_api/log_views/v1/get_log_view.ts diff --git a/x-pack/plugins/infra/common/http_api/log_views/v1/index.ts b/x-pack/plugins/logs_shared/common/http_api/log_views/v1/index.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_views/v1/index.ts rename to x-pack/plugins/logs_shared/common/http_api/log_views/v1/index.ts diff --git a/x-pack/plugins/infra/common/http_api/log_views/v1/put_log_view.ts b/x-pack/plugins/logs_shared/common/http_api/log_views/v1/put_log_view.ts similarity index 100% rename from x-pack/plugins/infra/common/http_api/log_views/v1/put_log_view.ts rename to x-pack/plugins/logs_shared/common/http_api/log_views/v1/put_log_view.ts diff --git a/x-pack/plugins/logs_shared/common/index.ts b/x-pack/plugins/logs_shared/common/index.ts new file mode 100644 index 0000000000000..07f029868b22a --- /dev/null +++ b/x-pack/plugins/logs_shared/common/index.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// LogView runtime +export { + defaultFilterStateKey, + defaultPositionStateKey, + DEFAULT_LOG_VIEW, + DEFAULT_REFRESH_INTERVAL, + logDataViewReferenceRT, + logIndexNameReferenceRT, + logViewColumnConfigurationRT, + logViewReferenceRT, + persistedLogViewReferenceRT, + defaultLogViewAttributes, +} from './log_views'; + +// LogView types +export type { + LogDataViewReference, + LogIndexNameReference, + LogIndexReference, + LogView, + LogViewAttributes, + LogViewColumnConfiguration, + LogViewReference, + LogViewStatus, + PersistedLogViewReference, + ResolvedLogView, + ResolvedLogViewField, +} from './log_views'; + +// LogView errors +export { + FetchLogViewError, + FetchLogViewStatusError, + ResolveLogViewError, +} from './log_views/errors'; + +// eslint-disable-next-line @kbn/eslint/no_export_all +export * from './log_entry'; + +// Http types +export type { LogEntriesSummaryBucket, LogEntriesSummaryHighlightsBucket } from './http_api'; + +// Http runtime +export { + LOG_ENTRIES_HIGHLIGHTS_PATH, + LOG_ENTRIES_SUMMARY_PATH, + logEntriesHighlightsRequestRT, + logEntriesHighlightsResponseRT, + logEntriesSummaryRequestRT, + logEntriesSummaryResponseRT, +} from './http_api'; diff --git a/x-pack/plugins/infra/common/log_entry/index.ts b/x-pack/plugins/logs_shared/common/log_entry/index.ts similarity index 100% rename from x-pack/plugins/infra/common/log_entry/index.ts rename to x-pack/plugins/logs_shared/common/log_entry/index.ts diff --git a/x-pack/plugins/infra/common/log_entry/log_entry.ts b/x-pack/plugins/logs_shared/common/log_entry/log_entry.ts similarity index 100% rename from x-pack/plugins/infra/common/log_entry/log_entry.ts rename to x-pack/plugins/logs_shared/common/log_entry/log_entry.ts diff --git a/x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts b/x-pack/plugins/logs_shared/common/log_entry/log_entry_cursor.ts similarity index 100% rename from x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts rename to x-pack/plugins/logs_shared/common/log_entry/log_entry_cursor.ts diff --git a/x-pack/plugins/logs_shared/common/log_text_scale/index.ts b/x-pack/plugins/logs_shared/common/log_text_scale/index.ts new file mode 100644 index 0000000000000..11ae6d0adc7ff --- /dev/null +++ b/x-pack/plugins/logs_shared/common/log_text_scale/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './log_text_scale'; diff --git a/x-pack/plugins/logs_shared/common/log_text_scale/log_text_scale.ts b/x-pack/plugins/logs_shared/common/log_text_scale/log_text_scale.ts new file mode 100644 index 0000000000000..7fc774c4b59e0 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/log_text_scale/log_text_scale.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type TextScale = 'small' | 'medium' | 'large'; + +export function isTextScale(maybeTextScale: string): maybeTextScale is TextScale { + return ['small', 'medium', 'large'].includes(maybeTextScale); +} diff --git a/x-pack/plugins/infra/common/log_views/defaults.ts b/x-pack/plugins/logs_shared/common/log_views/defaults.ts similarity index 82% rename from x-pack/plugins/infra/common/log_views/defaults.ts rename to x-pack/plugins/logs_shared/common/log_views/defaults.ts index 155ce6465b2fa..2d7f26c6d9407 100644 --- a/x-pack/plugins/infra/common/log_views/defaults.ts +++ b/x-pack/plugins/logs_shared/common/log_views/defaults.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { defaultSourceConfiguration } from '../source_configuration/defaults'; -import { LogViewAttributes, LogViewsStaticConfig } from './types'; +import { DefaultLogViewsStaticConfig, LogViewAttributes } from './types'; export const defaultLogViewId = 'default'; export const defaultFilterStateKey = 'logFilter'; @@ -41,8 +40,8 @@ export const defaultLogViewAttributes: LogViewAttributes = { ], }; -export const defaultLogViewsStaticConfig: LogViewsStaticConfig = { - messageFields: defaultSourceConfiguration.fields.message, +export const defaultLogViewsStaticConfig: DefaultLogViewsStaticConfig = { + messageFields: ['message', '@message'], }; export const DEFAULT_LOG_VIEW = { diff --git a/x-pack/plugins/infra/common/log_views/errors.ts b/x-pack/plugins/logs_shared/common/log_views/errors.ts similarity index 100% rename from x-pack/plugins/infra/common/log_views/errors.ts rename to x-pack/plugins/logs_shared/common/log_views/errors.ts diff --git a/x-pack/plugins/infra/common/log_views/index.ts b/x-pack/plugins/logs_shared/common/log_views/index.ts similarity index 89% rename from x-pack/plugins/infra/common/log_views/index.ts rename to x-pack/plugins/logs_shared/common/log_views/index.ts index 22176058622c0..dd0cdaece4316 100644 --- a/x-pack/plugins/infra/common/log_views/index.ts +++ b/x-pack/plugins/logs_shared/common/log_views/index.ts @@ -9,4 +9,3 @@ export * from './defaults'; export * from './errors'; export * from './resolved_log_view'; export * from './types'; -export * from './url_state_storage_service'; diff --git a/x-pack/plugins/infra/common/log_views/log_view.mock.ts b/x-pack/plugins/logs_shared/common/log_views/log_view.mock.ts similarity index 100% rename from x-pack/plugins/infra/common/log_views/log_view.mock.ts rename to x-pack/plugins/logs_shared/common/log_views/log_view.mock.ts diff --git a/x-pack/plugins/infra/common/log_views/resolved_log_view.mock.ts b/x-pack/plugins/logs_shared/common/log_views/resolved_log_view.mock.ts similarity index 100% rename from x-pack/plugins/infra/common/log_views/resolved_log_view.mock.ts rename to x-pack/plugins/logs_shared/common/log_views/resolved_log_view.mock.ts diff --git a/x-pack/plugins/infra/common/log_views/resolved_log_view.ts b/x-pack/plugins/logs_shared/common/log_views/resolved_log_view.ts similarity index 91% rename from x-pack/plugins/infra/common/log_views/resolved_log_view.ts rename to x-pack/plugins/logs_shared/common/log_views/resolved_log_view.ts index 391c6be18fb9d..b9419fbd51f22 100644 --- a/x-pack/plugins/infra/common/log_views/resolved_log_view.ts +++ b/x-pack/plugins/logs_shared/common/log_views/resolved_log_view.ts @@ -8,6 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView, DataViewsContract, FieldSpec } from '@kbn/data-views-plugin/common'; import { TIEBREAKER_FIELD, TIMESTAMP_FIELD } from '../constants'; +import { defaultLogViewsStaticConfig } from './defaults'; import { ResolveLogViewError } from './errors'; import { LogViewAttributes, LogViewColumnConfiguration, LogViewsStaticConfig } from './types'; @@ -26,16 +27,16 @@ export interface ResolvedLogView { dataViewReference: DataView; } -export const resolveLogView = async ( +export const resolveLogView = ( logViewId: string, logViewAttributes: LogViewAttributes, dataViewsService: DataViewsContract, config: LogViewsStaticConfig ): Promise => { if (logViewAttributes.logIndices.type === 'index_name') { - return await resolveLegacyReference(logViewId, logViewAttributes, dataViewsService, config); + return resolveLegacyReference(logViewId, logViewAttributes, dataViewsService, config); } else { - return await resolveDataViewReference(logViewAttributes, dataViewsService); + return resolveDataViewReference(logViewAttributes, dataViewsService); } }; @@ -71,7 +72,7 @@ const resolveLegacyReference = async ( indices, timestampField: TIMESTAMP_FIELD, tiebreakerField: TIEBREAKER_FIELD, - messageField: config.messageFields, + messageField: config.messageFields ?? defaultLogViewsStaticConfig.messageFields, fields: dataViewReference.fields, runtimeMappings: {}, columns: logViewAttributes.logColumns, diff --git a/x-pack/plugins/infra/common/log_views/types.ts b/x-pack/plugins/logs_shared/common/log_views/types.ts similarity index 96% rename from x-pack/plugins/infra/common/log_views/types.ts rename to x-pack/plugins/logs_shared/common/log_views/types.ts index ca6dd95330f8c..f94601f9e0f84 100644 --- a/x-pack/plugins/infra/common/log_views/types.ts +++ b/x-pack/plugins/logs_shared/common/log_views/types.ts @@ -7,10 +7,12 @@ import * as rt from 'io-ts'; -export interface LogViewsStaticConfig { +export interface DefaultLogViewsStaticConfig { messageFields: string[]; } +export type LogViewsStaticConfig = Partial; + export const logViewOriginRT = rt.keyof({ stored: null, internal: null, diff --git a/x-pack/plugins/logs_shared/common/mocks.ts b/x-pack/plugins/logs_shared/common/mocks.ts new file mode 100644 index 0000000000000..06ff3df3eb00d --- /dev/null +++ b/x-pack/plugins/logs_shared/common/mocks.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { createResolvedLogViewMock } from './log_views/resolved_log_view.mock'; diff --git a/x-pack/plugins/logs_shared/common/runtime_types.ts b/x-pack/plugins/logs_shared/common/runtime_types.ts new file mode 100644 index 0000000000000..89a6b4323b89a --- /dev/null +++ b/x-pack/plugins/logs_shared/common/runtime_types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RouteValidationFunction } from '@kbn/core/server'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts'; + +type ErrorFactory = (message: string) => Error; + +const getErrorPath = ([first, ...rest]: Context): string[] => { + if (typeof first === 'undefined') { + return []; + } else if (first.type instanceof IntersectionType) { + const [, ...next] = rest; + return getErrorPath(next); + } else if (first.type instanceof UnionType) { + const [, ...next] = rest; + return [first.key, ...getErrorPath(next)]; + } + + return [first.key, ...getErrorPath(rest)]; +}; + +const getErrorType = ({ context }: ValidationError) => + context[context.length - 1]?.type?.name ?? 'unknown'; + +const formatError = (error: ValidationError) => + error.message ?? + `in ${getErrorPath(error.context).join('/')}: ${JSON.stringify( + error.value + )} does not match expected type ${getErrorType(error)}`; + +export const formatErrors = (errors: ValidationError[]) => + `Failed to validate: \n${errors.map((error) => ` ${formatError(error)}`).join('\n')}`; + +export const createPlainError = (message: string) => new Error(message); + +export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { + throw createError(formatErrors(errors)); +}; + +export const decodeOrThrow = + ( + runtimeType: Type, + createError: ErrorFactory = createPlainError + ) => + (inputValue: InputValue) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); + +type ValdidationResult = ReturnType>; + +export const createValidationFunction = + ( + runtimeType: Type + ): RouteValidationFunction => + (inputValue, { badRequest, ok }) => + pipe( + runtimeType.decode(inputValue), + fold>( + (errors: Errors) => badRequest(formatErrors(errors)), + (result: DecodedValue) => ok(result) + ) + ); diff --git a/x-pack/plugins/logs_shared/common/search_strategies/common/errors.ts b/x-pack/plugins/logs_shared/common/search_strategies/common/errors.ts new file mode 100644 index 0000000000000..4114af07619dc --- /dev/null +++ b/x-pack/plugins/logs_shared/common/search_strategies/common/errors.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 * as rt from 'io-ts'; + +const abortedRequestSearchStrategyErrorRT = rt.type({ + type: rt.literal('aborted'), +}); + +export type AbortedRequestSearchStrategyError = rt.TypeOf< + typeof abortedRequestSearchStrategyErrorRT +>; + +const genericSearchStrategyErrorRT = rt.type({ + type: rt.literal('generic'), + message: rt.string, +}); + +export type GenericSearchStrategyError = rt.TypeOf; + +const shardFailureSearchStrategyErrorRT = rt.type({ + type: rt.literal('shardFailure'), + shardInfo: rt.type({ + shard: rt.union([rt.number, rt.null]), + index: rt.union([rt.string, rt.null]), + node: rt.union([rt.string, rt.null]), + }), + message: rt.union([rt.string, rt.null]), +}); + +export type ShardFailureSearchStrategyError = rt.TypeOf; + +export const searchStrategyErrorRT = rt.union([ + abortedRequestSearchStrategyErrorRT, + genericSearchStrategyErrorRT, + shardFailureSearchStrategyErrorRT, +]); + +export type SearchStrategyError = rt.TypeOf; diff --git a/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entries.ts b/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entries.ts new file mode 100644 index 0000000000000..f8daaa1b9227b --- /dev/null +++ b/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entries.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import * as rt from 'io-ts'; +import { + logEntryAfterCursorRT, + logEntryBeforeCursorRT, + logEntryCursorRT, + logEntryRT, +} from '../../log_entry'; +import { logViewColumnConfigurationRT, logViewReferenceRT } from '../../log_views'; +import { jsonObjectRT } from '../../typed_json'; +import { searchStrategyErrorRT } from '../common/errors'; + +export const LOG_ENTRIES_SEARCH_STRATEGY = 'infra-log-entries'; + +const logEntriesBaseSearchRequestParamsRT = rt.intersection([ + rt.type({ + logView: logViewReferenceRT, + startTimestamp: rt.number, + endTimestamp: rt.number, + size: rt.number, + }), + rt.partial({ + query: jsonObjectRT, + columns: rt.array(logViewColumnConfigurationRT), + highlightPhrase: rt.string, + }), +]); + +export const logEntriesBeforeSearchRequestParamsRT = rt.intersection([ + logEntriesBaseSearchRequestParamsRT, + logEntryBeforeCursorRT, +]); + +export const logEntriesAfterSearchRequestParamsRT = rt.intersection([ + logEntriesBaseSearchRequestParamsRT, + logEntryAfterCursorRT, +]); + +export const logEntriesSearchRequestParamsRT = rt.union([ + logEntriesBaseSearchRequestParamsRT, + logEntriesBeforeSearchRequestParamsRT, + logEntriesAfterSearchRequestParamsRT, +]); + +export type LogEntriesSearchRequestParams = rt.TypeOf; + +export type LogEntriesSearchRequestQuery = estypes.QueryDslQueryContainer; + +export const logEntriesSearchResponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + entries: rt.array(logEntryRT), + topCursor: rt.union([logEntryCursorRT, rt.null]), + bottomCursor: rt.union([logEntryCursorRT, rt.null]), + }), + rt.partial({ + hasMoreBefore: rt.boolean, + hasMoreAfter: rt.boolean, + }), + ]), + }), + rt.partial({ + errors: rt.array(searchStrategyErrorRT), + }), +]); + +export type LogEntriesSearchResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entry.ts b/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entry.ts new file mode 100644 index 0000000000000..6d2a7891264d1 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/search_strategies/log_entries/log_entry.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { logEntryCursorRT, logEntryFieldRT } from '../../log_entry'; +import { logViewReferenceRT } from '../../log_views'; +import { searchStrategyErrorRT } from '../common/errors'; + +export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; + +export const logEntrySearchRequestParamsRT = rt.type({ + logView: logViewReferenceRT, + logEntryId: rt.string, +}); + +export type LogEntrySearchRequestParams = rt.TypeOf; + +export const logEntryRT = rt.type({ + id: rt.string, + index: rt.string, + fields: rt.array(logEntryFieldRT), + cursor: logEntryCursorRT, +}); + +export type LogEntry = rt.TypeOf; + +export const logEntrySearchResponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.union([logEntryRT, rt.null]), + }), + rt.partial({ + errors: rt.array(searchStrategyErrorRT), + }), +]); + +export type LogEntrySearchResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/logs_shared/common/time/index.ts b/x-pack/plugins/logs_shared/common/time/index.ts new file mode 100644 index 0000000000000..c6a68995c024a --- /dev/null +++ b/x-pack/plugins/logs_shared/common/time/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './time_key'; diff --git a/x-pack/plugins/logs_shared/common/time/time_key.ts b/x-pack/plugins/logs_shared/common/time/time_key.ts new file mode 100644 index 0000000000000..4c78158dd5bf6 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/time/time_key.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ascending, bisector } from 'd3-array'; +import * as rt from 'io-ts'; + +export const minimalTimeKeyRT = rt.type({ + time: rt.number, + tiebreaker: rt.number, +}); + +export const timeKeyRT = rt.intersection([ + minimalTimeKeyRT, + rt.partial({ + gid: rt.string, + fromAutoReload: rt.boolean, + }), +]); +export type TimeKey = rt.TypeOf; + +export interface UniqueTimeKey extends TimeKey { + gid: string; +} + +export type Comparator = (firstValue: any, secondValue: any) => number; + +export function compareTimeKeys( + firstKey: TimeKey, + secondKey: TimeKey, + compareValues: Comparator = ascending +): number { + const timeComparison = compareValues(firstKey.time, secondKey.time); + + if (timeComparison === 0) { + const tiebreakerComparison = compareValues(firstKey.tiebreaker, secondKey.tiebreaker); + + if ( + tiebreakerComparison === 0 && + typeof firstKey.gid !== 'undefined' && + typeof secondKey.gid !== 'undefined' + ) { + return compareValues(firstKey.gid, secondKey.gid); + } + + return tiebreakerComparison; + } + + return timeComparison; +} + +export const compareToTimeKey = + (keyAccessor: (value: Value) => TimeKey, compareValues?: Comparator) => + (value: Value, key: TimeKey) => + compareTimeKeys(keyAccessor(value), key, compareValues); + +export const getIndexAtTimeKey = ( + keyAccessor: (value: Value) => TimeKey, + compareValues?: Comparator +) => { + const comparator = compareToTimeKey(keyAccessor, compareValues); + const collectionBisector = bisector(comparator); + + return (collection: Value[], key: TimeKey): number | null => { + const index = collectionBisector.left(collection, key); + + if (index >= collection.length) { + return null; + } + + if (comparator(collection[index], key) !== 0) { + return null; + } + + return index; + }; +}; diff --git a/x-pack/plugins/logs_shared/common/typed_json.ts b/x-pack/plugins/logs_shared/common/typed_json.ts new file mode 100644 index 0000000000000..95d5d4274c3b6 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/typed_json.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 * as rt from 'io-ts'; +import { JsonArray, JsonObject, JsonValue } from '@kbn/utility-types'; + +export type { JsonArray, JsonObject, JsonValue }; + +export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]); +export type JsonScalar = rt.TypeOf; + +export const jsonValueRT: rt.Type = rt.recursion('JsonValue', () => + rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT]) +); + +export const jsonArrayRT: rt.Type = rt.recursion('JsonArray', () => + rt.array(jsonValueRT) +); + +export const jsonObjectRT: rt.Type = rt.recursion('JsonObject', () => + rt.record(rt.string, jsonValueRT) +); diff --git a/x-pack/plugins/ess_security/public/get_started/jest.config.js b/x-pack/plugins/logs_shared/jest.config.js similarity index 52% rename from x-pack/plugins/ess_security/public/get_started/jest.config.js rename to x-pack/plugins/logs_shared/jest.config.js index 3dc5ab6b07f08..4d4168f2dbda4 100644 --- a/x-pack/plugins/ess_security/public/get_started/jest.config.js +++ b/x-pack/plugins/logs_shared/jest.config.js @@ -7,10 +7,11 @@ module.exports = { preset: '@kbn/test', - rootDir: '../../../../..', - roots: ['/x-pack/plugins/ess_security/public/get_started'], - coverageDirectory: - '/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/get_started', + rootDir: '../../..', + roots: ['/x-pack/plugins/logs_shared'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/logs_shared', coverageReporters: ['text', 'html'], - collectCoverageFrom: ['/x-pack/plugins/ess_security/public/get_started/**/*.{ts,tsx}'], + collectCoverageFrom: [ + '/x-pack/plugins/logs_shared/{common,public,server}/**/*.{ts,tsx}', + ], }; diff --git a/x-pack/plugins/logs_shared/kibana.jsonc b/x-pack/plugins/logs_shared/kibana.jsonc new file mode 100644 index 0000000000000..fea2ccc878485 --- /dev/null +++ b/x-pack/plugins/logs_shared/kibana.jsonc @@ -0,0 +1,22 @@ +{ + "type": "plugin", + "id": "@kbn/logs-shared-plugin", + "owner": "@elastic/infra-monitoring-ui", + "description": "Exposes the shared components and APIs to access and visualize logs.", + "plugin": { + "id": "logsShared", + "server": true, + "browser": true, + "configPath": ["xpack", "logs_shared"], + "requiredPlugins": ["data", "dataViews", "usageCollection", "observabilityShared"], + "optionalPlugins": ["observability"], + "requiredBundles": [ + "observability", + "kibanaUtils", + "kibanaReact", + ], + "extraPublicDirs": [ + "common", + ] + } +} diff --git a/x-pack/plugins/logs_shared/public/components/auto_sizer.tsx b/x-pack/plugins/logs_shared/public/components/auto_sizer.tsx new file mode 100644 index 0000000000000..a983502fa85b7 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/components/auto_sizer.tsx @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEqual } from 'lodash'; +import React from 'react'; + +interface Measurement { + width?: number; + height?: number; +} + +interface Measurements { + bounds: Measurement; + content: Measurement; +} + +interface AutoSizerProps { + detectAnyWindowResize?: boolean | 'height' | 'width'; + bounds?: boolean; + content?: boolean; + onResize?: (size: Measurements) => void; + children: ( + args: { measureRef: (instance: HTMLElement | null) => any } & Measurements + ) => React.ReactNode; +} + +interface AutoSizerState { + boundsMeasurement: Measurement; + contentMeasurement: Measurement; +} + +export class AutoSizer extends React.PureComponent { + public element: HTMLElement | null = null; + public resizeObserver: ResizeObserver | null = null; + public windowWidth: number = -1; + public windowHeight: number = -1; + + public readonly state = { + boundsMeasurement: { + height: void 0, + width: void 0, + }, + contentMeasurement: { + height: void 0, + width: void 0, + }, + }; + + constructor(props: AutoSizerProps) { + super(props); + if (this.props.detectAnyWindowResize) { + window.addEventListener('resize', this.updateMeasurement); + } + this.resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => { + entries.forEach((entry) => { + if (entry.target === this.element) { + this.measure(entry); + } + }); + }); + } + + public componentWillUnmount() { + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + if (this.props.detectAnyWindowResize) { + window.removeEventListener('resize', this.updateMeasurement); + } + } + + public measure = (entry: ResizeObserverEntry | null) => { + if (!this.element) { + return; + } + + const { content = true, bounds = false } = this.props; + const { + boundsMeasurement: previousBoundsMeasurement, + contentMeasurement: previousContentMeasurement, + } = this.state; + + const boundsRect = bounds ? this.element.getBoundingClientRect() : null; + const boundsMeasurement = boundsRect + ? { + height: boundsRect.height, + width: boundsRect.width, + } + : previousBoundsMeasurement; + + if (this.props.detectAnyWindowResize && boundsMeasurement) { + if ( + boundsMeasurement.width && + this.windowWidth !== -1 && + this.windowWidth > window.innerWidth + ) { + const gap = this.windowWidth - window.innerWidth; + boundsMeasurement.width = boundsMeasurement.width - gap; + } + if ( + boundsMeasurement.height && + this.windowHeight !== -1 && + this.windowHeight > window.innerHeight + ) { + const gap = this.windowHeight - window.innerHeight; + boundsMeasurement.height = boundsMeasurement.height - gap; + } + } + this.windowWidth = window.innerWidth; + this.windowHeight = window.innerHeight; + const contentRect = content && entry ? entry.contentRect : null; + const contentMeasurement = + contentRect && entry + ? { + height: entry.contentRect.height, + width: entry.contentRect.width, + } + : previousContentMeasurement; + if ( + isEqual(boundsMeasurement, previousBoundsMeasurement) && + isEqual(contentMeasurement, previousContentMeasurement) + ) { + return; + } + + requestAnimationFrame(() => { + if (!this.resizeObserver) { + return; + } + + this.setState({ boundsMeasurement, contentMeasurement }); + + if (this.props.onResize) { + this.props.onResize({ + bounds: boundsMeasurement, + content: contentMeasurement, + }); + } + }); + }; + + public render() { + const { children } = this.props; + const { boundsMeasurement, contentMeasurement } = this.state; + return children({ + bounds: boundsMeasurement, + content: contentMeasurement, + measureRef: this.storeRef, + }); + } + + private updateMeasurement = () => + requestAnimationFrame(() => { + const { detectAnyWindowResize } = this.props; + if (!detectAnyWindowResize) return; + switch (detectAnyWindowResize) { + case 'height': + if (this.windowHeight !== window.innerHeight) { + this.measure(null); + } + break; + case 'width': + if (this.windowWidth !== window.innerWidth) { + this.measure(null); + } + break; + default: + this.measure(null); + } + }); + + private storeRef = (element: HTMLElement | null) => { + if (this.element && this.resizeObserver) { + this.resizeObserver.unobserve(this.element); + } + + if (element && this.resizeObserver) { + this.resizeObserver.observe(element); + } + + this.element = element; + }; +} diff --git a/x-pack/plugins/infra/public/components/centered_flyout_body.tsx b/x-pack/plugins/logs_shared/public/components/centered_flyout_body.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/centered_flyout_body.tsx rename to x-pack/plugins/logs_shared/public/components/centered_flyout_body.tsx diff --git a/x-pack/plugins/infra/public/components/data_search_error_callout.stories.tsx b/x-pack/plugins/logs_shared/public/components/data_search_error_callout.stories.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/data_search_error_callout.stories.tsx rename to x-pack/plugins/logs_shared/public/components/data_search_error_callout.stories.tsx diff --git a/x-pack/plugins/infra/public/components/data_search_error_callout.tsx b/x-pack/plugins/logs_shared/public/components/data_search_error_callout.tsx similarity index 92% rename from x-pack/plugins/infra/public/components/data_search_error_callout.tsx rename to x-pack/plugins/logs_shared/public/components/data_search_error_callout.tsx index dd63b22db683d..52e36002de73f 100644 --- a/x-pack/plugins/infra/public/components/data_search_error_callout.tsx +++ b/x-pack/plugins/logs_shared/public/components/data_search_error_callout.tsx @@ -35,7 +35,7 @@ export const DataSearchErrorCallout: React.FC<{ onClick={onRetry} > @@ -59,7 +59,7 @@ const AbortedRequestErrorMessage: React.FC<{ }> = ({}) => ( ); @@ -73,7 +73,7 @@ const ShardFailureErrorMessage: React.FC<{ error: ShardFailureSearchStrategyErro }) => ( void; + testString?: string; +} + +export const NoData: React.FC = ({ + titleText, + bodyText, + refetchText, + onRefetch, + testString, +}) => ( + {titleText}} + titleSize="m" + body={

{bodyText}

} + actions={ + + {refetchText} + + } + data-test-subj={testString} + /> +); + +const CenteredEmptyPrompt = euiStyled(EuiEmptyPrompt)` + align-self: center; +`; diff --git a/x-pack/plugins/infra/public/components/formatted_time.tsx b/x-pack/plugins/logs_shared/public/components/formatted_time.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/formatted_time.tsx rename to x-pack/plugins/logs_shared/public/components/formatted_time.tsx diff --git a/x-pack/plugins/logs_shared/public/components/loading/__examples__/index.stories.tsx b/x-pack/plugins/logs_shared/public/components/loading/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..879d8a2328ebc --- /dev/null +++ b/x-pack/plugins/logs_shared/public/components/loading/__examples__/index.stories.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react/types-6-0'; +import React from 'react'; +import { LogsSharedLoadingPanel } from '..'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme'; + +export default { + title: 'logs_shared/LogsSharedLoadingPanel', + decorators: [ + (wrappedStory) =>
{wrappedStory()}
, + decorateWithGlobalStorybookThemeProviders, + ], +} as Meta; + +export const LoadingPanel: Story = () => ( + +); diff --git a/x-pack/plugins/logs_shared/public/components/loading/index.tsx b/x-pack/plugins/logs_shared/public/components/loading/index.tsx new file mode 100644 index 0000000000000..ca032885a8958 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/components/loading/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingChart, EuiPanel, EuiText } from '@elastic/eui'; +import * as React from 'react'; + +import { euiStyled } from '@kbn/kibana-react-plugin/common'; + +interface LogsSharedLoadingProps { + text: string | JSX.Element; + height: number | string; + width: number | string; +} + +export class LogsSharedLoadingPanel extends React.PureComponent { + public render() { + const { height, text, width } = this.props; + return ( + + + + + +

{text}

+
+
+
+
+ ); + } +} + +export const LogsSharedLoadingStaticPanel = euiStyled.div` + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; +`; + +export const LogsSharedLoadingStaticContentPanel = euiStyled.div` + flex: 0 0 auto; + align-self: center; + text-align: center; +`; diff --git a/x-pack/plugins/infra/public/components/log_stream/index.ts b/x-pack/plugins/logs_shared/public/components/log_stream/index.ts similarity index 100% rename from x-pack/plugins/infra/public/components/log_stream/index.ts rename to x-pack/plugins/logs_shared/public/components/log_stream/index.ts diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.mdx similarity index 91% rename from x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx rename to x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.mdx index b6e1d5f7fe0d8..36bddaf0a11d2 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx +++ b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.mdx @@ -1,15 +1,15 @@ import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; - + # Embeddable `` component The purpose of this component is to allow you, the developer, to have your very own Log Stream in your plugin. -The component is exposed through `infra/public`. Since Kibana uses relative paths, it is up to you to find how to import it (sorry). +The component is exposed through `logs_shared/public`. Since Kibana uses relative paths, it is up to you to find how to import it (sorry). ```tsx -import { LogStream } from '../../../../../../infra/public'; +import { LogStream } from '../../../../../../logs_shared/public'; // ^^ Modify appropriately ``` @@ -17,7 +17,7 @@ import { LogStream } from '../../../../../../infra/public'; To use the component your plugin needs to follow certain criteria: -- Ensure `"infra"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`. +- Ensure `"logsShared"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`. - Ensure the `` component is mounted inside the hierachy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). At a minimum, the kibana-react provider must pass `http` (from core start services) and `data` (from core plugin start dependencies). - Ensure the `` component is mounted inside the hierachy of a [`KibanaThemeProvider`](https://github.com/elastic/kibana/blob/31d2db035c905fb5819fa6dc2354f3be795a34cf/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx#L27). - Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx). This is not the same as the provider exported by EUI. It bridges the gap between EUI and styled components and predates the css-in-js support in EUI. @@ -35,7 +35,7 @@ const startTimestamp = endTimestamp - 15 * 60 * 1000; // 15 minutes This will show a list of log entries between the specified timestamps. - + ## Query log entries @@ -80,7 +80,7 @@ The component also has a `filters` prop that accepts valid es-query `filters`. T ## Center the view on a specific entry -By default the component will load at the bottom of the list, showing the newest entries. You can change the rendering point with the `center` prop. The prop takes a [`LogEntriesCursor`](https://github.com/elastic/kibana/blob/0a6c748cc837c016901f69ff05d81395aa2d41c8/x-pack/plugins/infra/common/http_api/log_entries/common.ts#L9-L13). +By default the component will load at the bottom of the list, showing the newest entries. You can change the rendering point with the `center` prop. The prop takes a [`LogEntriesCursor`](https://github.com/elastic/kibana/blob/0a6c748cc837c016901f69ff05d81395aa2d41c8/x-pack/plugins/logs_shared/common/http_api/log_entries/common.ts#L9-L13). ```tsx ``` - + ## Highlight a specific entry @@ -100,7 +100,7 @@ The component can highlight a specific line via the `highlight` prop. It takes t ``` - + ## Column configuration @@ -131,7 +131,7 @@ The easiest way is to specify what columns you want with the `columns` prop. /> ``` - + The rendering of the column headers and the cell contents can also be customized with the following properties: @@ -206,7 +206,7 @@ The rendering of the column headers and the cell contents can also be customized /> ``` - + ### With a static log view configuration @@ -262,4 +262,4 @@ The component can render a button on the row that opens a flyout, via the `showF showFlyoutAction /> ``` - + diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.tsx b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/log_stream/log_stream.stories.tsx rename to x-pack/plugins/logs_shared/public/components/log_stream/log_stream.stories.tsx diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.story_decorators.tsx b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.story_decorators.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/log_stream/log_stream.story_decorators.tsx rename to x-pack/plugins/logs_shared/public/components/log_stream/log_stream.story_decorators.tsx diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/log_stream/log_stream.tsx rename to x-pack/plugins/logs_shared/public/components/log_stream/log_stream.tsx diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_error_boundary.tsx b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream_error_boundary.tsx similarity index 93% rename from x-pack/plugins/infra/public/components/log_stream/log_stream_error_boundary.tsx rename to x-pack/plugins/logs_shared/public/components/log_stream/log_stream_error_boundary.tsx index c2e025dcd5e75..cf60b902e3b33 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_error_boundary.tsx +++ b/x-pack/plugins/logs_shared/public/components/log_stream/log_stream_error_boundary.tsx @@ -33,7 +33,7 @@ const LogStreamErrorContent: React.FC<{ @@ -46,7 +46,7 @@ const LogStreamErrorContent: React.FC<{ diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/index.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/index.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_entry_flyout/index.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/index.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx similarity index 90% rename from x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx index e06149dd90d5c..e99304a61b453 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; -import { getApmTraceUrl } from '@kbn/observability-plugin/public'; import { useLinkProps, LinkDescriptor } from '@kbn/observability-shared-plugin/public'; import { useVisibilityState } from '../../../utils/use_visibility_state'; import { LogEntry } from '../../../../common/search_strategies/log_entries/log_entry'; @@ -45,7 +44,7 @@ export const LogEntryActionsMenu = ({ logEntry }: LogEntryActionsMenuProps) => { {...uptimeLinkProps} > , @@ -57,7 +56,7 @@ export const LogEntryActionsMenu = ({ logEntry }: LogEntryActionsMenuProps) => { {...apmLinkProps} > , @@ -78,7 +77,7 @@ export const LogEntryActionsMenu = ({ logEntry }: LogEntryActionsMenuProps) => { onClick={toggle} > @@ -142,3 +141,15 @@ const getAPMLink = (logEntry: LogEntry): LinkDescriptor | undefined => { pathname: getApmTraceUrl({ traceId, rangeFrom, rangeTo }), }; }; + +function getApmTraceUrl({ + traceId, + rangeFrom, + rangeTo, +}: { + traceId: string; + rangeFrom: string; + rangeTo: string; +}) { + return `/link-to/trace/${traceId}?` + new URLSearchParams({ rangeFrom, rangeTo }).toString(); +} diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx similarity index 90% rename from x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx index a503c05011246..09b8381859354 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx @@ -47,7 +47,7 @@ export const LogEntryFieldsTable: React.FC<{ () => [ { field: 'field', - name: i18n.translate('xpack.infra.logFlyout.fieldColumnLabel', { + name: i18n.translate('xpack.logsShared.logFlyout.fieldColumnLabel', { defaultMessage: 'Field', }), sortable: true, @@ -66,7 +66,7 @@ export const LogEntryFieldsTable: React.FC<{ }, { field: 'value', - name: i18n.translate('xpack.infra.logFlyout.valueColumnLabel', { + name: i18n.translate('xpack.logsShared.logFlyout.valueColumnLabel', { defaultMessage: 'Value', }), render: (_name: string, item: LogEntryField) => ( @@ -107,11 +107,11 @@ const searchOptions = { }, }; -const setFilterButtonLabel = i18n.translate('xpack.infra.logFlyout.filterAriaLabel', { +const setFilterButtonLabel = i18n.translate('xpack.logsShared.logFlyout.filterAriaLabel', { defaultMessage: 'Filter', }); -const setFilterButtonDescription = i18n.translate('xpack.infra.logFlyout.setFilterTooltip', { +const setFilterButtonDescription = i18n.translate('xpack.logsShared.logFlyout.setFilterTooltip', { defaultMessage: 'View event with filter', }); diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx similarity index 91% rename from x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index 8237a36f5557d..e06064c676a63 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -146,7 +146,7 @@ export const LogEntryFlyout = ({

{logEntryId} : '', }} @@ -158,7 +158,7 @@ export const LogEntryFlyout = ({ {logEntry.index}, @@ -237,18 +237,24 @@ export const LogEntryFlyout = ({ ); }; -const explainLogMessageTitle = i18n.translate('xpack.infra.logFlyout.explainLogMessageTitle', { +const explainLogMessageTitle = i18n.translate('xpack.logsShared.logFlyout.explainLogMessageTitle', { defaultMessage: "What's this message?", }); -const similarLogMessagesTitle = i18n.translate('xpack.infra.logFlyout.similarLogMessagesTitle', { - defaultMessage: 'How do I find similar log messages?', -}); +const similarLogMessagesTitle = i18n.translate( + 'xpack.logsShared.logFlyout.similarLogMessagesTitle', + { + defaultMessage: 'How do I find similar log messages?', + } +); -const loadingProgressMessage = i18n.translate('xpack.infra.logFlyout.loadingMessage', { +const loadingProgressMessage = i18n.translate('xpack.logsShared.logFlyout.loadingMessage', { defaultMessage: 'Searching log entry in shards', }); -const loadingErrorCalloutTitle = i18n.translate('xpack.infra.logFlyout.loadingErrorCalloutTitle', { - defaultMessage: 'Error while searching the log entry', -}); +const loadingErrorCalloutTitle = i18n.translate( + 'xpack.logsShared.logFlyout.loadingErrorCalloutTitle', + { + defaultMessage: 'Error while searching the log entry', + } +); diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/column_headers.tsx similarity index 96% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/column_headers.tsx index 30355ea1a9a23..9e1d9330f7b87 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/column_headers.tsx @@ -43,7 +43,7 @@ export const LogColumnHeaders: React.FunctionComponent<{ } else { columnHeader = firstVisiblePosition ? localizedDate(firstVisiblePosition.time) - : i18n.translate('xpack.infra.logs.stream.timestampColumnTitle', { + : i18n.translate('xpack.logsShared.logs.stream.timestampColumnTitle', { defaultMessage: 'Timestamp', }); } @@ -64,7 +64,7 @@ export const LogColumnHeaders: React.FunctionComponent<{ } else if (typeof columnConfiguration.messageColumn.header === 'string') { columnHeader = columnConfiguration.messageColumn.header; } else { - columnHeader = i18n.translate('xpack.infra.logs.stream.messageColumnTitle', { + columnHeader = i18n.translate('xpack.logsShared.logs.stream.messageColumnTitle', { defaultMessage: 'Message', }); } diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/field_value.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/field_value.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/highlighting.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/highlighting.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/highlighting.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/highlighting.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/index.ts similarity index 79% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/index.ts index dbd5bd49d0240..dfccaae6dd8c9 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/index.ts @@ -5,11 +5,14 @@ * 2.0. */ +export type { LogEntryStreamItem } from './item'; export type { LogEntryColumnWidths } from './log_entry_column'; -export { LogEntryColumn, useColumnWidths, iconColumnId } from './log_entry_column'; + +export { LogColumnHeader, LogColumnHeadersWrapper } from './column_headers'; +export { iconColumnId, LogEntryColumn, useColumnWidths } from './log_entry_column'; +export { LogEntryContextMenu } from './log_entry_context_menu'; export { LogEntryFieldColumn } from './log_entry_field_column'; export { LogEntryMessageColumn } from './log_entry_message_column'; export { LogEntryRowWrapper } from './log_entry_row'; export { LogEntryTimestampColumn } from './log_entry_timestamp_column'; export { ScrollableLogTextStreamView } from './scrollable_log_text_stream_view'; -export { LogEntryContextMenu } from './log_entry_context_menu'; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/item.ts similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/item.ts diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/jump_to_tail.tsx similarity index 93% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/jump_to_tail.tsx index 81bb3f299db57..1f3f35d0d1a17 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/jump_to_tail.tsx @@ -24,7 +24,7 @@ export class LogTextStreamJumpToTail extends React.PureComponent @@ -36,7 +36,7 @@ export class LogTextStreamJumpToTail extends React.PureComponent diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/loading_item_view.tsx similarity index 89% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/loading_item_view.tsx index f0b0fc4236bfc..9cd7c91250528 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/loading_item_view.tsx @@ -118,19 +118,19 @@ const ProgressMessage: React.FC = ({ timestamp, position, const message = position === 'start' ? ( ) : isStreaming ? ( ) : ( @@ -152,12 +152,12 @@ const ProgressSpinner: React.FC<{ kind: 'streaming' | 'loading' }> = ({ kind }) {kind === 'streaming' ? ( ) : ( )} @@ -182,7 +182,7 @@ const ProgressCta: React.FC = ({ if (rangeEdge === 'now' && position === 'end') { return ( - + ); } @@ -217,7 +217,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'ms': return ( @@ -225,7 +225,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 's': return ( @@ -233,7 +233,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'm': return ( @@ -241,7 +241,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'h': return ( @@ -249,7 +249,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'd': return ( @@ -257,7 +257,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'w': return ( @@ -265,7 +265,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'M': return ( @@ -273,7 +273,7 @@ const ProgressExtendMessage: React.FC<{ amount: number; unit: Unit }> = ({ amoun case 'y': return ( diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_date_row.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_date_row.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_context_menu.tsx similarity index 97% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_context_menu.tsx index ba87fa8c09bc4..17c7fb9dd7e9f 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_context_menu.tsx @@ -35,7 +35,7 @@ interface LogEntryContextMenuProps { } const DEFAULT_MENU_LABEL = i18n.translate( - 'xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', + 'xpack.logsShared.logEntryItemView.logEntryActionsMenuToolTip', { defaultMessage: 'View actions for line', } diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_field_column.test.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_field_column.test.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_field_column.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_field_column.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_message_column.test.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_message_column.test.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_message_column.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_message_column.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_row.tsx similarity index 97% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_row.tsx index 4a137ca361a40..2963a3e1aac14 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_row.tsx @@ -32,16 +32,16 @@ import { LogEntryMessageColumn } from './log_entry_message_column'; import { LogEntryTimestampColumn } from './log_entry_timestamp_column'; import { highlightedContentStyle, hoveredContentStyle, monospaceTextStyle } from './text_styles'; -const MENU_LABEL = i18n.translate('xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', { +const MENU_LABEL = i18n.translate('xpack.logsShared.logEntryItemView.logEntryActionsMenuToolTip', { defaultMessage: 'View actions for line', }); -const LOG_DETAILS_LABEL = i18n.translate('xpack.infra.logs.logEntryActionsDetailsButton', { +const LOG_DETAILS_LABEL = i18n.translate('xpack.logsShared.logs.logEntryActionsDetailsButton', { defaultMessage: 'View details', }); const LOG_VIEW_IN_CONTEXT_LABEL = i18n.translate( - 'xpack.infra.lobs.logEntryActionsViewInContextButton', + 'xpack.logsShared.lobs.logEntryActionsViewInContextButton', { defaultMessage: 'View in context', } diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_text_separator.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_text_separator.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/log_text_separator.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/log_text_separator.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/measurable_item_view.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/measurable_item_view.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/measurable_item_view.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/measurable_item_view.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx similarity index 95% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 4c11bd21daa19..8a0a120a3a7d5 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -16,7 +16,7 @@ import { TimeKey, UniqueTimeKey } from '../../../../common/time'; import { callWithoutRepeats } from '../../../utils/handlers'; import { AutoSizer } from '../../auto_sizer'; import { NoData } from '../../empty_states'; -import { InfraLoadingPanel } from '../../loading'; +import { LogsSharedLoadingPanel } from '../../loading'; import { getStreamItemBeforeTimeKey, getStreamItemId, parseStreamItemId, StreamItem } from './item'; import { LogColumnHeaders } from './column_headers'; import { LogTextStreamLoadingItemView } from './loading_item_view'; @@ -160,27 +160,30 @@ export class ScrollableLogTextStreamView extends React.PureComponent< return ( {isReloading && (!isStreaming || !hasItems) ? ( - } /> ) : !hasItems ? ( @@ -368,6 +371,9 @@ export class ScrollableLogTextStreamView extends React.PureComponent< }; } +// eslint-disable-next-line import/no-default-export +export default ScrollableLogTextStreamView; + /** * If the above component wasn't a class component, this wouldn't be necessary * since the `useColumnWidths` hook could have been used directly. diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/text_styles.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/text_styles.tsx diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_text_stream/vertical_scroll_panel.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx rename to x-pack/plugins/logs_shared/public/components/logging/log_text_stream/vertical_scroll_panel.tsx diff --git a/x-pack/plugins/infra/public/components/resettable_error_boundary.tsx b/x-pack/plugins/logs_shared/public/components/resettable_error_boundary.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/resettable_error_boundary.tsx rename to x-pack/plugins/logs_shared/public/components/resettable_error_boundary.tsx diff --git a/x-pack/plugins/infra/public/containers/logs/log_entry.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_entry.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_entry.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_entry.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/index.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/index.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/index.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/index.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_entry_highlights.tsx similarity index 98% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_entry_highlights.tsx index ea0e1fa326c78..6e13ff542add6 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useMemo, useState } from 'react'; -import { LogViewReference } from '../../../../common/log_views'; +import { LogViewReference } from '../../../../common'; import { LogEntriesHighlightsResponse } from '../../../../common/http_api'; import { LogEntry } from '../../../../common/log_entry'; import { TimeKey } from '../../../../common/time'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_highlights.tsx similarity index 96% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_highlights.tsx index 0a6710731bcb1..aa963925d7ac9 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_highlights.tsx @@ -8,12 +8,13 @@ import createContainer from 'constate'; import { useState } from 'react'; import useThrottle from 'react-use/lib/useThrottle'; -import { LogViewReference } from '../../../../common/log_views'; + +import { LogViewReference } from '../../../../common'; import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; -import { useLogPositionStateContext } from '../log_position'; import { TimeKey } from '../../../../common/time'; +import { useLogPositionStateContext } from '../log_position'; const FETCH_THROTTLE_INTERVAL = 3000; @@ -25,7 +26,7 @@ interface UseLogHighlightsStateProps { filterQuery: string | null; } -export const useLogHighlightsState = ({ +const useLogHighlightsState = ({ logViewReference, sourceVersion, centerCursor, diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_summary_highlights.ts similarity index 97% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_summary_highlights.ts index 8c5f7fb7ae778..61a1a02618e7a 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/log_summary_highlights.ts @@ -8,7 +8,7 @@ import { useEffect, useMemo, useState } from 'react'; import { debounce } from 'lodash'; -import { LogViewReference } from '../../../../common/log_views'; +import { LogViewReference } from '../../../../common'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { fetchLogSummaryHighlights } from './api/fetch_log_summary_highlights'; import { LogEntriesSummaryHighlightsResponse } from '../../../../common/http_api'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx b/x-pack/plugins/logs_shared/public/containers/logs/log_highlights/next_and_previous.tsx similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx rename to x-pack/plugins/logs_shared/public/containers/logs/log_highlights/next_and_previous.tsx diff --git a/x-pack/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_position/index.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_position/index.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_position/index.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_position/use_log_position.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_position/use_log_position.ts similarity index 80% rename from x-pack/plugins/infra/public/containers/logs/log_position/use_log_position.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_position/use_log_position.ts index 2471acf6e9283..236e59093d450 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_position/use_log_position.ts +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_position/use_log_position.ts @@ -5,15 +5,28 @@ * 2.0. */ +import { TimeRange } from '@kbn/es-query'; import createContainer from 'constate'; import { useMemo } from 'react'; -import { VisiblePositions } from '../../../observability_logs/log_stream_position_state/src/types'; -import { - LogStreamPageActorRef, - LogStreamPageCallbacks, -} from '../../../observability_logs/log_stream_page/state'; -import { MatchedStateFromActor } from '../../../observability_logs/xstate_helpers'; +import { ActorRefWithDeprecatedState } from 'xstate'; import { TimeKey } from '../../../../common/time'; +import { + MatchedStateFromActor, + OmitDeprecatedState, +} from '../../../observability_logs/xstate_helpers'; + +type LogStreamPageState = MatchedStateFromActor< + OmitDeprecatedState>, + { hasLogViewIndices: 'initialized' } +>; + +interface VisiblePositions { + startKey: TimeKey | null; + middleKey: TimeKey | null; + endKey: TimeKey | null; + pagesAfterEnd: number; + pagesBeforeStart: number; +} type TimeKeyOrNull = TimeKey | null; @@ -46,6 +59,15 @@ export interface LogPositionCallbacks { updateDateRange: UpdateDateRangeFn; } +export interface LogStreamPageCallbacks { + updateTimeRange: (timeRange: Partial) => void; + jumpToTargetPosition: (targetPosition: TimeKey | null) => void; + jumpToTargetPositionTime: (time: number) => void; + reportVisiblePositions: (visiblePositions: VisiblePositions) => void; + startLiveStreaming: () => void; + stopLiveStreaming: () => void; +} + type UpdateDateRangeFn = ( newDateRange: Partial> ) => void; @@ -54,7 +76,7 @@ export const useLogPositionState = ({ logStreamPageState, logStreamPageCallbacks, }: { - logStreamPageState: InitializedLogStreamPageState; + logStreamPageState: LogStreamPageState; logStreamPageCallbacks: LogStreamPageCallbacks; }): LogPositionStateParams & LogPositionCallbacks => { const dateRange = useMemo(() => getLegacyDateRange(logStreamPageState), [logStreamPageState]); @@ -119,7 +141,7 @@ export const useLogPositionState = ({ export const [LogPositionStateProvider, useLogPositionStateContext] = createContainer(useLogPositionState); -const getLegacyDateRange = (logStreamPageState: InitializedLogStreamPageState): DateRange => { +const getLegacyDateRange = (logStreamPageState: LogStreamPageState): DateRange => { return { startDateExpression: logStreamPageState.context.timeRange.from, endDateExpression: logStreamPageState.context.timeRange.to, @@ -130,8 +152,3 @@ const getLegacyDateRange = (logStreamPageState: InitializedLogStreamPageState): timestampsLastUpdate: logStreamPageState.context.timestamps.lastChangedTimestamp, }; }; - -type InitializedLogStreamPageState = MatchedStateFromActor< - LogStreamPageActorRef, - { hasLogViewIndices: 'initialized' } ->; diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_stream/index.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_stream/index.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_stream/index.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_after.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_after.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_around.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_around.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_around.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_around.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_before.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_stream/use_fetch_log_entries_before.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/api/fetch_log_summary.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/api/fetch_log_summary.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/bucket_size.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/bucket_size.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_summary/bucket_size.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/bucket_size.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/index.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/index.ts similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_summary/index.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/index.ts diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx similarity index 100% rename from x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/log_summary.tsx similarity index 97% rename from x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/log_summary.tsx index c2792300c5f34..0360d6d381ada 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/log_summary.tsx @@ -8,7 +8,7 @@ import { useEffect } from 'react'; import { exhaustMap, map, Observable } from 'rxjs'; import { HttpHandler } from '@kbn/core-http-browser'; -import { LogViewReference } from '../../../../common/log_views'; +import { LogViewReference } from '../../../../common'; import { useObservableState, useReplaySubject } from '../../../utils/use_observable'; import { fetchLogSummary } from './api/fetch_log_summary'; import { LogEntriesSummaryRequest, LogEntriesSummaryResponse } from '../../../../common/http_api'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/with_summary.ts similarity index 66% rename from x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts rename to x-pack/plugins/logs_shared/public/containers/logs/log_summary/with_summary.ts index 1dc5c7021d253..5d18926cca294 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts +++ b/x-pack/plugins/logs_shared/public/containers/logs/log_summary/with_summary.ts @@ -5,32 +5,24 @@ * 2.0. */ -import { useSelector } from '@xstate/react'; -import stringify from 'json-stable-stringify'; import useThrottle from 'react-use/lib/useThrottle'; -import { useLogViewContext } from '../../../hooks/use_log_view'; -import { useLogStreamPageStateContext } from '../../../observability_logs/log_stream_page/state'; +import { useLogPositionStateContext, useLogViewContext } from '../../..'; import { RendererFunction } from '../../../utils/typed_react'; -import { useLogPositionStateContext } from '../log_position'; import { LogSummaryBuckets, useLogSummary } from './log_summary'; const FETCH_THROTTLE_INTERVAL = 3000; -export const WithSummary = ({ - children, -}: { +export interface WithSummaryProps { + serializedParsedQuery: string | null; children: RendererFunction<{ buckets: LogSummaryBuckets; start: number | null; end: number | null; }>; -}) => { +} + +export const WithSummary = ({ serializedParsedQuery, children }: WithSummaryProps) => { const { logViewReference } = useLogViewContext(); - const serializedParsedQuery = useSelector(useLogStreamPageStateContext(), (logStreamPageState) => - logStreamPageState.matches({ hasLogViewIndices: 'initialized' }) - ? stringify(logStreamPageState.context.parsedQuery) - : null - ); const { startTimestamp, endTimestamp } = useLogPositionStateContext(); // Keep it reasonably updated for the `now` case, but don't reload all the time when the user scrolls diff --git a/x-pack/plugins/logs_shared/public/hooks/use_kibana.tsx b/x-pack/plugins/logs_shared/public/hooks/use_kibana.tsx new file mode 100644 index 0000000000000..09032b4b644a2 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/hooks/use_kibana.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PropsOf } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { CoreStart } from '@kbn/core/public'; +import { + createKibanaReactContext, + KibanaReactContextValue, + useKibana, +} from '@kbn/kibana-react-plugin/public'; +import { + LogsSharedClientCoreSetup, + LogsSharedClientStartDeps, + LogsSharedClientStartExports, +} from '../types'; + +export type PluginKibanaContextValue = CoreStart & + LogsSharedClientStartDeps & + LogsSharedClientStartExports; + +export const createKibanaContextForPlugin = ( + core: CoreStart, + plugins: LogsSharedClientStartDeps, + pluginStart: LogsSharedClientStartExports +) => + createKibanaReactContext({ + ...core, + ...plugins, + ...pluginStart, + }); + +export const useKibanaContextForPlugin = + useKibana as () => KibanaReactContextValue; + +export const useKibanaContextForPluginProvider = ( + core: CoreStart, + plugins: LogsSharedClientStartDeps, + pluginStart: LogsSharedClientStartExports +) => { + const { Provider } = useMemo( + () => createKibanaContextForPlugin(core, plugins, pluginStart), + [core, pluginStart, plugins] + ); + + return Provider; +}; + +export const createLazyComponentWithKibanaContext = >( + coreSetup: LogsSharedClientCoreSetup, + lazyComponentFactory: () => Promise<{ default: T }> +) => + React.lazy(() => + Promise.all([lazyComponentFactory(), coreSetup.getStartServices()]).then( + ([{ default: LazilyLoadedComponent }, [core, plugins, pluginStart]]) => { + const { Provider } = createKibanaContextForPlugin(core, plugins, pluginStart); + + return { + default: (props: PropsOf) => ( + + + + ), + }; + } + ) + ); diff --git a/x-pack/plugins/infra/public/hooks/use_log_view.ts b/x-pack/plugins/logs_shared/public/hooks/use_log_view.ts similarity index 100% rename from x-pack/plugins/infra/public/hooks/use_log_view.ts rename to x-pack/plugins/logs_shared/public/hooks/use_log_view.ts diff --git a/x-pack/plugins/logs_shared/public/index.ts b/x-pack/plugins/logs_shared/public/index.ts new file mode 100644 index 0000000000000..63692bbdeae54 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/index.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { dynamic } from '../common/dynamic'; +import { LogsSharedPlugin } from './plugin'; + +export type { + LogsSharedClientSetupExports, + LogsSharedClientStartExports, + LogsSharedClientSetupDeps, + LogsSharedClientStartDeps, +} from './types'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new LogsSharedPlugin(); +} + +// Containers & Hook +export { LogViewProvider, useLogViewContext, useLogView } from './hooks/use_log_view'; +export { LogStreamProvider, useLogStreamContext } from './containers/logs/log_stream'; +export { + LogPositionStateProvider, + useLogPositionStateContext, +} from './containers/logs/log_position'; +export { + LogHighlightsStateProvider, + useLogHighlightsStateContext, +} from './containers/logs/log_highlights'; +export type { LogSummaryBuckets, WithSummaryProps } from './containers/logs/log_summary'; +export { useLogSummary, WithSummary } from './containers/logs/log_summary'; +export { useLogEntryFlyout } from './components/logging/log_entry_flyout'; + +// Shared components +export type { + LogEntryStreamItem, + LogEntryColumnWidths, +} from './components/logging/log_text_stream'; +export { + iconColumnId, + useColumnWidths, +} from './components/logging/log_text_stream/log_entry_column'; +export { LogEntryFlyout } from './components/logging/log_entry_flyout'; +export type { LogStreamProps } from './components/log_stream/log_stream'; + +export const LogStream = dynamic(() => import('./components/log_stream/log_stream')); +export const LogColumnHeader = dynamic( + () => import('./components/logging/log_text_stream/column_headers') +); +export const LogColumnHeadersWrapper = dynamic( + () => import('./components/logging/log_text_stream/column_headers') +); +export const LogEntryColumn = dynamic( + () => import('./components/logging/log_text_stream/log_entry_column') +); +export const LogEntryContextMenu = dynamic( + () => import('./components/logging/log_text_stream/log_entry_context_menu') +); +export const LogEntryFieldColumn = dynamic( + () => import('./components/logging/log_text_stream/log_entry_field_column') +); +export const LogEntryMessageColumn = dynamic( + () => import('./components/logging/log_text_stream/log_entry_message_column') +); +export const LogEntryRowWrapper = dynamic( + () => import('./components/logging/log_text_stream/log_entry_row') +); +export const LogEntryTimestampColumn = dynamic( + () => import('./components/logging/log_text_stream/log_entry_timestamp_column') +); +export const ScrollableLogTextStreamView = dynamic( + () => import('./components/logging/log_text_stream/scrollable_log_text_stream_view') +); + +// State machine utils +export { + getLogViewReferenceFromUrl, + initializeFromUrl, + listenForUrlChanges, + updateContextInUrl, +} from './observability_logs/log_view_state'; +export type { + LogViewContextWithError, + LogViewContextWithResolvedLogView, + LogViewNotificationChannel, + LogViewNotificationEvent, +} from './observability_logs/log_view_state'; diff --git a/x-pack/plugins/logs_shared/public/mocks.tsx b/x-pack/plugins/logs_shared/public/mocks.tsx new file mode 100644 index 0000000000000..963480d8fd90f --- /dev/null +++ b/x-pack/plugins/logs_shared/public/mocks.tsx @@ -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 { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock'; +import { LogsSharedClientStartExports } from './types'; + +export const createLogsSharedPluginStartMock = (): jest.Mocked => ({ + logViews: createLogViewsServiceStartMock(), +}); + +export const _ensureTypeCompatibility = (): LogsSharedClientStartExports => + createLogsSharedPluginStartMock(); diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/README.md b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/README.md similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/README.md rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/README.md diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/index.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/index.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/index.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/index.ts diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/src/index.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/index.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/src/index.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/index.ts diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/src/notifications.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/notifications.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/src/notifications.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/notifications.ts diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/src/state_machine.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/state_machine.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/src/state_machine.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/state_machine.ts diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/src/types.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/types.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/src/types.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/types.ts diff --git a/x-pack/plugins/infra/public/observability_logs/log_view_state/src/url_state_storage_service.ts b/x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/url_state_storage_service.ts similarity index 100% rename from x-pack/plugins/infra/public/observability_logs/log_view_state/src/url_state_storage_service.ts rename to x-pack/plugins/logs_shared/public/observability_logs/log_view_state/src/url_state_storage_service.ts diff --git a/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/README.md b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/README.md new file mode 100644 index 0000000000000..337f65add4226 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/README.md @@ -0,0 +1,3 @@ +# @kbn/observability-logs-xstate-helpers + +Helpers to design well-typed state machines with XState diff --git a/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/index.ts b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/index.ts new file mode 100644 index 0000000000000..3b2a320ae181f --- /dev/null +++ b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './src'; diff --git a/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/index.ts b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/index.ts new file mode 100644 index 0000000000000..340087bb31fc8 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './notification_channel'; +export * from './types'; diff --git a/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/notification_channel.ts b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/notification_channel.ts new file mode 100644 index 0000000000000..0108ab0225176 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/notification_channel.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ReplaySubject } from 'rxjs'; +import { ActionFunction, EventObject, Expr, Subscribable } from 'xstate'; + +export interface NotificationChannel { + createService: () => Subscribable; + notify: ( + eventExpr: Expr + ) => ActionFunction; +} + +export const createNotificationChannel = < + TContext, + TEvent extends EventObject, + TSentEvent +>(): NotificationChannel => { + const eventsSubject = new ReplaySubject(1); + + const createService = () => eventsSubject.asObservable(); + + const notify = + (eventExpr: Expr) => + (context: TContext, event: TEvent) => { + const eventToSend = eventExpr(context, event); + + if (eventToSend != null) { + eventsSubject.next(eventToSend); + } + }; + + return { + createService, + notify, + }; +}; diff --git a/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/types.ts b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/src/types.ts new file mode 100644 index 0000000000000..05e75c5fe6e45 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/observability_logs/xstate_helpers/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActorRef, ActorRefWithDeprecatedState, EmittedFrom, State, StateValue } from 'xstate'; + +export type OmitDeprecatedState> = Omit< + T, + 'state' +>; + +export type MatchedState< + TState extends State, + TStateValue extends StateValue +> = TState extends State< + any, + infer TEvent, + infer TStateSchema, + infer TTypestate, + infer TResolvedTypesMeta +> + ? State< + (TTypestate extends any + ? { value: TStateValue; context: any } extends TTypestate + ? TTypestate + : never + : never)['context'], + TEvent, + TStateSchema, + TTypestate, + TResolvedTypesMeta + > & { + value: TStateValue; + } + : never; + +export type MatchedStateFromActor< + TActorRef extends ActorRef, + TStateValue extends StateValue +> = MatchedState, TStateValue>; diff --git a/x-pack/plugins/logs_shared/public/plugin.ts b/x-pack/plugins/logs_shared/public/plugin.ts new file mode 100644 index 0000000000000..47ded2e3df835 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/plugin.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 { CoreStart } from '@kbn/core/public'; +import { LogViewsService } from './services/log_views'; +import { LogsSharedClientPluginClass, LogsSharedClientStartDeps } from './types'; + +export class LogsSharedPlugin implements LogsSharedClientPluginClass { + private logViews: LogViewsService; + + constructor() { + this.logViews = new LogViewsService(); + } + + public setup() { + const logViews = this.logViews.setup(); + + return { logViews }; + } + + public start(core: CoreStart, plugins: LogsSharedClientStartDeps) { + const logViews = this.logViews.start({ + http: core.http, + dataViews: plugins.dataViews, + search: plugins.data.search, + }); + + return { + logViews, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/infra/public/services/log_views/index.ts b/x-pack/plugins/logs_shared/public/services/log_views/index.ts similarity index 100% rename from x-pack/plugins/infra/public/services/log_views/index.ts rename to x-pack/plugins/logs_shared/public/services/log_views/index.ts diff --git a/x-pack/plugins/infra/public/services/log_views/log_views_client.mock.ts b/x-pack/plugins/logs_shared/public/services/log_views/log_views_client.mock.ts similarity index 100% rename from x-pack/plugins/infra/public/services/log_views/log_views_client.mock.ts rename to x-pack/plugins/logs_shared/public/services/log_views/log_views_client.mock.ts diff --git a/x-pack/plugins/infra/public/services/log_views/log_views_client.ts b/x-pack/plugins/logs_shared/public/services/log_views/log_views_client.ts similarity index 100% rename from x-pack/plugins/infra/public/services/log_views/log_views_client.ts rename to x-pack/plugins/logs_shared/public/services/log_views/log_views_client.ts diff --git a/x-pack/plugins/infra/public/services/log_views/log_views_service.mock.ts b/x-pack/plugins/logs_shared/public/services/log_views/log_views_service.mock.ts similarity index 100% rename from x-pack/plugins/infra/public/services/log_views/log_views_service.mock.ts rename to x-pack/plugins/logs_shared/public/services/log_views/log_views_service.mock.ts diff --git a/x-pack/plugins/infra/public/services/log_views/log_views_service.ts b/x-pack/plugins/logs_shared/public/services/log_views/log_views_service.ts similarity index 61% rename from x-pack/plugins/infra/public/services/log_views/log_views_service.ts rename to x-pack/plugins/logs_shared/public/services/log_views/log_views_service.ts index 9e081a8df5028..712196c95205c 100644 --- a/x-pack/plugins/infra/public/services/log_views/log_views_service.ts +++ b/x-pack/plugins/logs_shared/public/services/log_views/log_views_service.ts @@ -5,17 +5,23 @@ * 2.0. */ -import { LogViewsStaticConfig } from '../../../common/log_views'; +import { defaultLogViewsStaticConfig, LogViewsStaticConfig } from '../../../common/log_views'; import { LogViewsClient } from './log_views_client'; import { LogViewsServiceStartDeps, LogViewsServiceSetup, LogViewsServiceStart } from './types'; export class LogViewsService { - constructor(private readonly config: LogViewsStaticConfig) {} + private logViewsStaticConfig: LogViewsStaticConfig = defaultLogViewsStaticConfig; - public setup(): LogViewsServiceSetup {} + public setup(): LogViewsServiceSetup { + return { + setLogViewsStaticConfig: (config: LogViewsStaticConfig) => { + this.logViewsStaticConfig = config; + }, + }; + } public start({ dataViews, http, search }: LogViewsServiceStartDeps): LogViewsServiceStart { - const client = new LogViewsClient(dataViews, http, search.search, this.config); + const client = new LogViewsClient(dataViews, http, search.search, this.logViewsStaticConfig); return { client, diff --git a/x-pack/plugins/infra/public/services/log_views/types.ts b/x-pack/plugins/logs_shared/public/services/log_views/types.ts similarity index 90% rename from x-pack/plugins/infra/public/services/log_views/types.ts rename to x-pack/plugins/logs_shared/public/services/log_views/types.ts index 6fd7a4d208b3c..58a504be789bc 100644 --- a/x-pack/plugins/infra/public/services/log_views/types.ts +++ b/x-pack/plugins/logs_shared/public/services/log_views/types.ts @@ -12,11 +12,14 @@ import { LogView, LogViewAttributes, LogViewReference, + LogViewsStaticConfig, LogViewStatus, ResolvedLogView, } from '../../../common/log_views'; -export type LogViewsServiceSetup = void; +export interface LogViewsServiceSetup { + setLogViewsStaticConfig: (config: LogViewsStaticConfig) => void; +} export interface LogViewsServiceStart { client: ILogViewsClient; diff --git a/x-pack/plugins/logs_shared/public/test_utils/entries.ts b/x-pack/plugins/logs_shared/public/test_utils/entries.ts new file mode 100644 index 0000000000000..4dc3732fd49d5 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/test_utils/entries.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import faker from 'faker'; +import { LogEntry } from '../../common/log_entry'; +import { LogViewColumnConfiguration } from '../../common/log_views'; + +export const ENTRIES_EMPTY = { + data: { + entries: [], + topCursor: null, + bottomCursor: null, + }, +}; + +export function generateFakeEntries( + count: number, + startTimestamp: number, + endTimestamp: number, + columns: LogViewColumnConfiguration[] +): LogEntry[] { + const entries: LogEntry[] = []; + const timestampStep = Math.floor((endTimestamp - startTimestamp) / count); + for (let i = 0; i < count; i++) { + const timestamp = i === count - 1 ? endTimestamp : startTimestamp + timestampStep * i; + entries.push({ + id: `entry-${i}`, + index: 'logs-fake', + context: {}, + cursor: { time: timestamp, tiebreaker: i }, + columns: columns.map((column) => { + if ('timestampColumn' in column) { + return { columnId: column.timestampColumn.id, timestamp }; + } else if ('messageColumn' in column) { + return { + columnId: column.messageColumn.id, + message: [{ field: 'message', value: [fakeColumnValue('message')], highlights: [] }], + }; + } else { + return { + columnId: column.fieldColumn.id, + field: column.fieldColumn.field, + value: [fakeColumnValue(column.fieldColumn.field)], + highlights: [], + }; + } + }), + }); + } + + return entries; +} + +function fakeColumnValue(field: string): string { + switch (field) { + case 'message': + return faker.fake( + '{{internet.ip}} - [{{date.past}}] "GET {{internet.url}} HTTP/1.1" 200 {{random.number}} "-" "{{internet.userAgent}}"' + ); + case 'event.dataset': + return faker.fake('{{hacker.noun}}.{{hacker.noun}}'); + case 'log.file.path': + return faker.system.filePath(); + case 'log.level': + return faker.random.arrayElement(['debug', 'info', 'warn', 'error']); + case 'host.name': + return faker.hacker.noun(); + default: + return faker.lorem.sentence(); + } +} diff --git a/x-pack/plugins/logs_shared/public/test_utils/use_global_storybook_theme.tsx b/x-pack/plugins/logs_shared/public/test_utils/use_global_storybook_theme.tsx new file mode 100644 index 0000000000000..4d1feb4617dcf --- /dev/null +++ b/x-pack/plugins/logs_shared/public/test_utils/use_global_storybook_theme.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DecoratorFn } from '@storybook/react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import type { CoreTheme } from '@kbn/core/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + +type StoryContext = Parameters[1]; + +export const useGlobalStorybookTheme = ({ globals: { euiTheme } }: StoryContext) => { + const theme = useMemo(() => euiThemeFromId(euiTheme), [euiTheme]); + const [theme$] = useState(() => new BehaviorSubject(theme)); + + useEffect(() => { + theme$.next(theme); + }, [theme$, theme]); + + return { + theme, + theme$, + }; +}; + +export const GlobalStorybookThemeProviders: React.FC<{ storyContext: StoryContext }> = ({ + children, + storyContext, +}) => { + const { theme, theme$ } = useGlobalStorybookTheme(storyContext); + return ( + + {children} + + ); +}; + +export const decorateWithGlobalStorybookThemeProviders: DecoratorFn = ( + wrappedStory, + storyContext +) => ( + + {wrappedStory()} + +); + +const euiThemeFromId = (themeId: string): CoreTheme => { + switch (themeId) { + case 'v8.dark': + return { darkMode: true }; + default: + return { darkMode: false }; + } +}; diff --git a/x-pack/plugins/logs_shared/public/types.ts b/x-pack/plugins/logs_shared/public/types.ts new file mode 100644 index 0000000000000..f2563ccb3433f --- /dev/null +++ b/x-pack/plugins/logs_shared/public/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +// import type { OsqueryPluginStart } from '../../osquery/public'; +import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views'; + +// Our own setup and start contract values +export interface LogsSharedClientSetupExports { + logViews: LogViewsServiceSetup; +} + +export interface LogsSharedClientStartExports { + logViews: LogViewsServiceStart; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LogsSharedClientSetupDeps {} + +export interface LogsSharedClientStartDeps { + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + uiActions: UiActionsStart; +} + +export type LogsSharedClientCoreSetup = CoreSetup< + LogsSharedClientStartDeps, + LogsSharedClientStartExports +>; +export type LogsSharedClientCoreStart = CoreStart; +export type LogsSharedClientPluginClass = PluginClass< + LogsSharedClientSetupExports, + LogsSharedClientStartExports, + LogsSharedClientSetupDeps, + LogsSharedClientStartDeps +>; + +export type UnwrapPromise> = T extends Promise ? Value : never; + +export type LogsSharedClientStartServicesAccessor = LogsSharedClientCoreSetup['getStartServices']; +export type LogsSharedClientStartServices = UnwrapPromise< + ReturnType +>; diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/data_search.stories.mdx b/x-pack/plugins/logs_shared/public/utils/data_search/data_search.stories.mdx new file mode 100644 index 0000000000000..a8854692caa36 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/data_search.stories.mdx @@ -0,0 +1,152 @@ +import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks'; + + + +# The `data` plugin and `SearchStrategies` + +The search functionality abstraction provided by the `search` service of the +`data` plugin is pretty powerful: + +- The execution of the request is delegated to a search strategy, which is + executed on the Kibana server side. +- Any plugin can register custom search strategies with custom parameters and + response shapes. +- Search requests can be cancelled via an `AbortSignal`. +- Search requests are decoupled from the transport layer. The service will poll + for new results transparently. +- Partial responses can be returned as they become available if the search + takes longer. + +# Working with `data.search.search()` in the Browser + +The following chapters describe a set of React components and hooks that aim to +make it easy to take advantage of these characteristics from client-side React +code. They implement a producer/consumer pattern that decouples the craeation +of search requests from the consumption of the responses. This keeps each +code-path small and encourages the use of reactive processing, which in turn +reduces the risk of race conditions and incorrect assumptions about the +response timing. + +## Issuing new requests + +The main API to issue new requests is the `data.search.search()` function. It +returns an `Observable` representing the stream of partial and final results +without the consumer having to know the underlying transport mechanisms. +Besides receiving a search-strategy-specific parameter object, it supports +selection of the search strategy as well an `AbortSignal` used for request +cancellation. + +The hook `useDataSearch()` is designed to ease the integration between the +`Observable` world and the React world. It uses the function it is given to +derive the parameters to use for the next search request. The request can then +be issued by calling the returned `search()` function. For each new request the +hook emits an object describing the request and its state in the `requests$` +`Observable`. + +Since the specific response shape depends on the data strategy used, the hook +takes a projection function, that is responsible for decoding the response in +an appropriate way. Because most response projections follow a similar pattern +there's a helper `normalizeDataSearchResponses(initialResponse, +parseRawResponse)`, which generates an RxJS operator, that... + +- emits an initial response containing the given `initialResponse` value +- applies `parseRawResponse` to the `rawResponse` property of each emitted response +- transforms transport layer errors as well as parsing errors into + `SearchStrategyError`s + +```typescript +const parseMyCustomSearchResponse = normalizeDataSearchResponses( + 'initial value', + decodeOrThrow(myCustomSearchResponsePayloadRT) +); + +const { search, requests$ } = useDataSearch({ + getRequest: useCallback((searchTerm: string) => ({ + request: { + params: { + searchTerm + }, + options: { + strategy: 'my-custom-search-strategy', + }, + }, + }), []), + parseResponses: parseMyCustomSearchResponse, +}); +``` + +## Executing requests and consuming the responses + +The response `Observable`s emitted by `data.search.search()` is "cold", so it +won't be executed unless a subscriber subscribes to it. And in order to cleanly +cancel and garbage collect the subscription it should be integrated with the +React component life-cycle. + +The `useLatestPartialDataSearchResponse()` does that in such a way that the +newest response observable is subscribed to and that any previous response +observables are unsubscribed from for proper cancellation if a new request has +been created. This uses RxJS's `switchMap()` operator under the hood. The hook +also makes sure that all observables are unsubscribed from on unmount. + +A request can fail due to various reasons that include servers-side errors, +Elasticsearch shard failures and network failures. The intention is to map all +of them to a common `SearchStrategyError` interface. While the +`useLatestPartialDataSearchResponse()` hook does that for errors emitted +natively by the response `Observable`, it's the responsibility of the +projection function to handle errors that are encoded in the response body, +which includes most server-side errors. Note that errors and partial results in +a response are not mutually exclusive. + +The request status (running, partial, etc), the response +and the errors are turned in to React component state so they can be used in +the usual rendering cycle: + +```typescript +const { + cancelRequest, + isRequestRunning, + isResponsePartial, + latestResponseData, + latestResponseErrors, + loaded, + total, +} = useLatestPartialDataSearchResponse(requests$); +``` + +## Representing the request state to the user + +After the values have been made available to the React rendering process using +the `useLatestPartialDataSearchResponse()` hook, normal component hierarchies +can be used to make the request state and result available to the user. The +following utility components can make that even easier. + +### Undetermined progress + +If `total` and `loaded` are not (yet) known, we can show an undetermined +progress bar. + + + + + +### Known progress + +If `total` and `loaded` are returned by the search strategy, they can be used +to show a progress bar with the option to cancel the request if it takes too +long. + + + + + +### Failed requests + +Assuming the errors are represented as an array of `SearchStrategyError`s in +the `latestResponseErrors` return value, they can be rendered as appropriate +for the respective part of the UI. For many cases a `EuiCallout` is suitable, +so the `DataSearchErrorCallout` can serve as a starting point: + + + + + diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/flatten_data_search_response.ts b/x-pack/plugins/logs_shared/public/utils/data_search/flatten_data_search_response.ts new file mode 100644 index 0000000000000..52f08f39bc2d4 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/flatten_data_search_response.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 { map } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '@kbn/data-plugin/public'; +import { ParsedDataSearchRequestDescriptor } from './types'; + +export const flattenDataSearchResponseDescriptor = < + Request extends IKibanaSearchRequest, + Response +>({ + abortController, + options, + request, + response$, +}: ParsedDataSearchRequestDescriptor) => + response$.pipe( + map((response) => { + return { + abortController, + options, + request, + response, + }; + }) + ); diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/index.ts b/x-pack/plugins/logs_shared/public/utils/data_search/index.ts new file mode 100644 index 0000000000000..bd1881a6935e9 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './flatten_data_search_response'; +export * from './normalize_data_search_responses'; +export * from './types'; +export * from './use_data_search_request'; +export * from './use_data_search_response_state'; +export * from './use_latest_partial_data_search_response'; diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/normalize_data_search_responses.ts b/x-pack/plugins/logs_shared/public/utils/data_search/normalize_data_search_responses.ts new file mode 100644 index 0000000000000..0056b7466759a --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/normalize_data_search_responses.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Observable, of } from 'rxjs'; +import { catchError, map, startWith } from 'rxjs/operators'; +import { IKibanaSearchResponse } from '@kbn/data-plugin/public'; +import { AbortError } from '@kbn/kibana-utils-plugin/public'; +import { SearchStrategyError } from '../../../common/search_strategies/common/errors'; +import { ParsedKibanaSearchResponse } from './types'; + +export type RawResponseParser = (rawResponse: RawResponse) => { + data: Response; + errors?: SearchStrategyError[]; +}; + +/** + * An operator factory that normalizes each {@link IKibanaSearchResponse} by + * parsing it into a {@link ParsedKibanaSearchResponse} and adding initial + * responses and error handling. + * + * @param initialResponse - The initial value to emit when a new request is + * handled. + * @param projectResponse - The projection function to apply to each response + * payload. It should validate that the response payload is of the type {@link + * RawResponse} and decode it to a {@link Response}. + * + * @return An operator that adds parsing and error handling transformations to + * each response payload using the arguments given above. + */ +export const normalizeDataSearchResponses = + ( + initialResponse: InitialResponse, + parseRawResponse: RawResponseParser + ) => + ( + response$: Observable> + ): Observable> => + response$.pipe( + map((response) => { + const { data, errors = [] } = parseRawResponse(response.rawResponse); + return { + data, + errors, + isPartial: response.isPartial ?? false, + isRunning: response.isRunning ?? false, + loaded: response.loaded, + total: response.total, + }; + }), + startWith({ + data: initialResponse, + errors: [], + isPartial: true, + isRunning: true, + loaded: 0, + total: undefined, + }), + catchError((error) => + of({ + data: initialResponse, + errors: [ + error instanceof AbortError + ? { + type: 'aborted' as const, + } + : { + type: 'generic' as const, + message: `${error.message ?? error}`, + }, + ], + isPartial: true, + isRunning: false, + loaded: 0, + total: undefined, + }) + ) + ); diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/types.ts b/x-pack/plugins/logs_shared/public/utils/data_search/types.ts new file mode 100644 index 0000000000000..3e2c6a15487b8 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/types.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 { Observable } from 'rxjs'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchOptions, +} from '@kbn/data-plugin/public'; +import { SearchStrategyError } from '../../../common/search_strategies/common/errors'; + +export interface DataSearchRequestDescriptor { + request: Request; + options: ISearchOptions; + response$: Observable>; + abortController: AbortController; +} + +export interface ParsedDataSearchRequestDescriptor< + Request extends IKibanaSearchRequest, + ResponseData +> { + request: Request; + options: ISearchOptions; + response$: Observable>; + abortController: AbortController; +} + +export interface ParsedKibanaSearchResponse { + total?: number; + loaded?: number; + isRunning: boolean; + isPartial: boolean; + data: ResponseData; + errors: SearchStrategyError[]; +} + +export interface ParsedDataSearchResponseDescriptor< + Request extends IKibanaSearchRequest, + Response +> { + request: Request; + options: ISearchOptions; + response: ParsedKibanaSearchResponse; + abortController: AbortController; +} diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.test.tsx b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.test.tsx new file mode 100644 index 0000000000000..06f8ca1b82eb7 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.test.tsx @@ -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 { act, renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { firstValueFrom, Observable, of, Subject } from 'rxjs'; +import { + DataPublicPluginStart, + IKibanaSearchResponse, + ISearchGeneric, + ISearchStart, +} from '@kbn/data-plugin/public'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { PluginKibanaContextValue } from '../../hooks/use_kibana'; +import { normalizeDataSearchResponses } from './normalize_data_search_responses'; +import { useDataSearch } from './use_data_search_request'; + +describe('useDataSearch hook', () => { + it('forwards the search function arguments to the getRequest function', async () => { + const dataMock = createDataPluginMock(); + const { Provider: KibanaContextProvider } = createKibanaReactContext< + Partial + >({ + data: dataMock, + }); + + const getRequest = jest.fn((_firstArgument: string, _secondArgument: string) => null); + + const { result } = renderHook( + () => + useDataSearch({ + getRequest, + parseResponses: noopParseResponse, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + act(() => { + result.current.search('first', 'second'); + }); + + expect(getRequest).toHaveBeenLastCalledWith('first', 'second'); + expect(dataMock.search.search).not.toHaveBeenCalled(); + }); + + it('creates search requests with the given params and options and parses the responses', async () => { + const dataMock = createDataPluginMock(); + const searchResponseMock$ = of({ + rawResponse: { + firstKey: 'firstValue', + }, + }); + dataMock.search.search.mockReturnValue(searchResponseMock$); + const { Provider: KibanaContextProvider } = createKibanaReactContext< + Partial + >({ + data: dataMock, + }); + + const getRequest = jest.fn((firstArgument: string, secondArgument: string) => ({ + request: { + params: { + firstArgument, + secondArgument, + }, + }, + options: { + strategy: 'test-search-strategy', + }, + })); + + const { result } = renderHook( + () => + useDataSearch({ + getRequest, + parseResponses: noopParseResponse, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + // the request execution is lazy + expect(dataMock.search.search).not.toHaveBeenCalled(); + + // execute requests$ observable + const firstRequestPromise = firstValueFrom(result.current.requests$); + + act(() => { + result.current.search('first', 'second'); + }); + + const firstRequest = await firstRequestPromise; + + expect(dataMock.search.search).toHaveBeenLastCalledWith( + { + params: { firstArgument: 'first', secondArgument: 'second' }, + }, + { + abortSignal: expect.any(Object), + strategy: 'test-search-strategy', + } + ); + expect(firstRequest).toHaveProperty('abortController', expect.any(Object)); + expect(firstRequest).toHaveProperty('request.params', { + firstArgument: 'first', + secondArgument: 'second', + }); + expect(firstRequest).toHaveProperty('options.strategy', 'test-search-strategy'); + expect(firstRequest).toHaveProperty('response$', expect.any(Observable)); + await expect(firstRequest.response$.toPromise()).resolves.toMatchObject({ + data: { + firstKey: 'firstValue', // because this specific response parser just copies the raw response + }, + errors: [], + }); + }); + + it('aborts the request when the response observable looses the last subscriber', async () => { + const dataMock = createDataPluginMock(); + const searchResponseMock$ = new Subject(); + dataMock.search.search.mockReturnValue(searchResponseMock$); + const { Provider: KibanaContextProvider } = createKibanaReactContext< + Partial + >({ + data: dataMock, + }); + + const getRequest = jest.fn((firstArgument: string, secondArgument: string) => ({ + request: { + params: { + firstArgument, + secondArgument, + }, + }, + options: { + strategy: 'test-search-strategy', + }, + })); + + const { result } = renderHook( + () => + useDataSearch({ + getRequest, + parseResponses: noopParseResponse, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + // the request execution is lazy + expect(dataMock.search.search).not.toHaveBeenCalled(); + + // execute requests$ observable + const firstRequestPromise = firstValueFrom(result.current.requests$); + + act(() => { + result.current.search('first', 'second'); + }); + + const firstRequest = await firstRequestPromise; + + // execute requests$ observable + const firstResponseSubscription = firstRequest.response$.subscribe({ + next: jest.fn(), + }); + + // get the abort signal + const [, firstRequestOptions] = dataMock.search.search.mock.calls[0]; + + expect(firstRequestOptions?.abortSignal?.aborted).toBe(false); + + // unsubscribe + firstResponseSubscription.unsubscribe(); + + expect(firstRequestOptions?.abortSignal?.aborted).toBe(true); + }); +}); + +const createDataPluginMock = () => { + const dataMock = dataPluginMock.createStartContract() as DataPublicPluginStart & { + search: ISearchStart & { search: jest.MockedFunction }; + }; + return dataMock; +}; + +const noopParseResponse = normalizeDataSearchResponses( + null, + (response: Response) => ({ data: response }) +); diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.ts b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.ts new file mode 100644 index 0000000000000..230c2d87e0654 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_request.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { OperatorFunction, ReplaySubject } from 'rxjs'; +import { share, tap } from 'rxjs/operators'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchOptions, +} from '@kbn/data-plugin/public'; +import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; +import { tapUnsubscribe, useObservable } from '../use_observable'; +import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; + +export type DataSearchRequestFactory = ( + ...args: Args +) => + | { + request: Request; + options: ISearchOptions; + } + | null + | undefined; + +type ParseResponsesOperator = OperatorFunction< + IKibanaSearchResponse, + ParsedKibanaSearchResponse +>; + +export const useDataSearch = < + RequestFactoryArgs extends any[], + RequestParams, + Request extends IKibanaSearchRequest, + RawResponse, + Response +>({ + getRequest, + parseResponses, +}: { + getRequest: DataSearchRequestFactory; + parseResponses: ParseResponsesOperator; +}) => { + const { services } = useKibanaContextForPlugin(); + const requests$ = useObservable( + () => new ReplaySubject>(1), + [] + ); + + const search = useCallback( + (...args: RequestFactoryArgs) => { + const requestArgs = getRequest(...args); + + if (requestArgs == null) { + return; + } + + const abortController = new AbortController(); + let isAbortable = true; + + const newRequestDescriptor = { + ...requestArgs, + abortController, + response$: services.data.search + .search>(requestArgs.request, { + abortSignal: abortController.signal, + ...requestArgs.options, + }) + .pipe( + // avoid aborting failed or completed requests + tap({ + error: () => { + isAbortable = false; + }, + complete: () => { + isAbortable = false; + }, + }), + tapUnsubscribe(() => { + if (isAbortable) { + abortController.abort(); + } + }), + parseResponses, + share() + ), + }; + + requests$.next(newRequestDescriptor); + + return newRequestDescriptor; + }, + [getRequest, services.data.search, parseResponses, requests$] + ); + + return { + requests$, + search, + }; +}; diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_response_state.ts b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_response_state.ts new file mode 100644 index 0000000000000..c8ec672beb842 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/use_data_search_response_state.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 { useCallback } from 'react'; +import { Observable } from 'rxjs'; +import { IKibanaSearchRequest } from '@kbn/data-plugin/public'; +import { useObservableState } from '../use_observable'; +import { ParsedDataSearchResponseDescriptor } from './types'; + +export const useDataSearchResponseState = < + Request extends IKibanaSearchRequest, + Response, + InitialResponse +>( + response$: Observable> +) => { + const { latestValue } = useObservableState(response$, undefined); + + const cancelRequest = useCallback(() => { + latestValue?.abortController.abort(); + }, [latestValue]); + + return { + cancelRequest, + isRequestRunning: latestValue?.response.isRunning ?? false, + isResponsePartial: latestValue?.response.isPartial ?? false, + latestResponseData: latestValue?.response.data, + latestResponseErrors: latestValue?.response.errors, + loaded: latestValue?.response.loaded, + total: latestValue?.response.total, + }; +}; diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx b/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx new file mode 100644 index 0000000000000..a48cf2c7ccdc4 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; +import { IKibanaSearchRequest } from '@kbn/data-plugin/public'; +import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; +import { useLatestPartialDataSearchResponse } from './use_latest_partial_data_search_response'; + +describe('useLatestPartialDataSearchResponse hook', () => { + it("subscribes to the latest request's response observable", () => { + const firstRequest = { + abortController: new AbortController(), + options: {}, + request: { params: 'firstRequestParam' }, + response$: new BehaviorSubject>({ + data: 'initial', + isRunning: true, + isPartial: true, + errors: [], + }), + }; + + const secondRequest = { + abortController: new AbortController(), + options: {}, + request: { params: 'secondRequestParam' }, + response$: new BehaviorSubject>({ + data: 'initial', + isRunning: true, + isPartial: true, + errors: [], + }), + }; + + const requests$ = new Subject< + ParsedDataSearchRequestDescriptor, string> + >(); + + const { result } = renderHook(() => useLatestPartialDataSearchResponse(requests$)); + + expect(result).toHaveProperty('current.isRequestRunning', false); + expect(result).toHaveProperty('current.latestResponseData', undefined); + + // first request is started + act(() => { + requests$.next(firstRequest); + }); + + expect(result).toHaveProperty('current.isRequestRunning', true); + expect(result).toHaveProperty('current.latestResponseData', 'initial'); + + // first response of the first request arrives + act(() => { + firstRequest.response$.next({ + data: 'request-1-response-1', + isRunning: true, + isPartial: true, + errors: [], + }); + }); + + expect(result).toHaveProperty('current.isRequestRunning', true); + expect(result).toHaveProperty('current.latestResponseData', 'request-1-response-1'); + + // second request is started before the second response of the first request arrives + act(() => { + requests$.next(secondRequest); + secondRequest.response$.next({ + data: 'request-2-response-1', + isRunning: true, + isPartial: true, + errors: [], + }); + }); + + expect(result).toHaveProperty('current.isRequestRunning', true); + expect(result).toHaveProperty('current.latestResponseData', 'request-2-response-1'); + + // second response of the second request arrives + act(() => { + secondRequest.response$.next({ + data: 'request-2-response-2', + isRunning: false, + isPartial: false, + errors: [], + }); + }); + + expect(result).toHaveProperty('current.isRequestRunning', false); + expect(result).toHaveProperty('current.latestResponseData', 'request-2-response-2'); + }); + + it("unsubscribes from the latest request's response observable on unmount", () => { + const onUnsubscribe = jest.fn(); + + const firstRequest = { + abortController: new AbortController(), + options: {}, + request: { params: 'firstRequestParam' }, + response$: new Observable>(() => { + return onUnsubscribe; + }), + }; + + const requests$ = + of, string>>(firstRequest); + + const { unmount } = renderHook(() => useLatestPartialDataSearchResponse(requests$)); + + expect(onUnsubscribe).not.toHaveBeenCalled(); + + unmount(); + + expect(onUnsubscribe).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.ts b/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.ts new file mode 100644 index 0000000000000..48ff61eb676b9 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.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 { Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '@kbn/data-plugin/public'; +import { useOperator } from '../use_observable'; +import { flattenDataSearchResponseDescriptor } from './flatten_data_search_response'; +import { ParsedDataSearchRequestDescriptor, ParsedDataSearchResponseDescriptor } from './types'; +import { useDataSearchResponseState } from './use_data_search_response_state'; + +export const useLatestPartialDataSearchResponse = ( + requests$: Observable> +) => { + const latestResponse$: Observable> = + useOperator(requests$, flattenLatestDataSearchResponse); + + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + latestResponseData, + latestResponseErrors, + loaded, + total, + } = useDataSearchResponseState(latestResponse$); + + return { + cancelRequest, + isRequestRunning, + isResponsePartial, + latestResponseData, + latestResponseErrors, + loaded, + total, + }; +}; + +const flattenLatestDataSearchResponse = switchMap(flattenDataSearchResponseDescriptor); diff --git a/x-pack/plugins/logs_shared/public/utils/datemath.ts b/x-pack/plugins/logs_shared/public/utils/datemath.ts new file mode 100644 index 0000000000000..ab07ace52d05d --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/datemath.ts @@ -0,0 +1,293 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import dateMath, { Unit } from '@kbn/datemath'; + +const JS_MAX_DATE = 8640000000000000; + +export function isValidDatemath(value: string): boolean { + const parsedValue = dateMath.parse(value); + return !!(parsedValue && parsedValue.isValid()); +} + +export function datemathToEpochMillis( + value: string, + round: 'down' | 'up' = 'down', + forceNow?: Date +): number | null { + const parsedValue = dateMath.parse(value, { roundUp: round === 'up', forceNow }); + if (!parsedValue || !parsedValue.isValid()) { + return null; + } + return parsedValue.valueOf(); +} + +type DatemathExtension = + | { + value: string; + diffUnit: Unit; + diffAmount: number; + } + | { value: 'now' }; + +const datemathNowExpression = /(\+|\-)(\d+)(ms|s|m|h|d|w|M|y)$/; + +/** + * Extend a datemath value + * @param value The value to extend + * @param {'before' | 'after'} direction Should the value move before or after in time + * @param oppositeEdge For absolute values, the value of the other edge of the range + */ +export function extendDatemath( + value: string, + direction: 'before' | 'after' = 'before', + oppositeEdge?: string +): DatemathExtension | undefined { + if (!isValidDatemath(value)) { + return undefined; + } + + // `now` cannot be extended + if (value === 'now') { + return { value: 'now' }; + } + + // The unit is relative + if (value.startsWith('now')) { + return extendRelativeDatemath(value, direction); + } else if (oppositeEdge && isValidDatemath(oppositeEdge)) { + return extendAbsoluteDatemath(value, direction, oppositeEdge); + } + + return undefined; +} + +function extendRelativeDatemath( + value: string, + direction: 'before' | 'after' +): DatemathExtension | undefined { + const [, operator, amount, unit] = datemathNowExpression.exec(value) || []; + if (!operator || !amount || !unit) { + return undefined; + } + + const mustIncreaseAmount = + (operator === '-' && direction === 'before') || (operator === '+' && direction === 'after'); + const parsedAmount = parseInt(amount, 10); + let newUnit: Unit = unit as Unit; + let newAmount: number; + + // Extend the amount + switch (unit) { + // For small units, always double or halve the amount + case 'ms': + case 's': + newAmount = mustIncreaseAmount ? parsedAmount * 2 : Math.floor(parsedAmount / 2); + break; + // For minutes, increase or decrease in doubles or halves, depending on + // the amount of minutes + case 'm': + let ratio; + const MINUTES_LARGE = 10; + if (mustIncreaseAmount) { + ratio = parsedAmount >= MINUTES_LARGE ? 0.5 : 1; + newAmount = parsedAmount + Math.floor(parsedAmount * ratio); + } else { + newAmount = + parsedAmount >= MINUTES_LARGE + ? Math.floor(parsedAmount / 1.5) + : parsedAmount - Math.floor(parsedAmount * 0.5); + } + break; + + // For hours, increase or decrease half an hour for 1 hour. Otherwise + // increase full hours + case 'h': + if (parsedAmount === 1) { + newAmount = mustIncreaseAmount ? 90 : 30; + newUnit = 'm'; + } else { + newAmount = mustIncreaseAmount ? parsedAmount + 1 : parsedAmount - 1; + } + break; + + // For the rest of units, increase or decrease one smaller unit for + // amounts of 1. Otherwise increase or decrease the unit + case 'd': + case 'w': + case 'M': + case 'y': + if (parsedAmount === 1) { + newUnit = dateMath.unitsDesc[dateMath.unitsDesc.indexOf(unit) + 1]; + newAmount = mustIncreaseAmount + ? convertDate(1, unit, newUnit) + 1 + : convertDate(1, unit, newUnit) - 1; + } else { + newAmount = mustIncreaseAmount ? parsedAmount + 1 : parsedAmount - 1; + } + break; + + default: + throw new TypeError('Unhandled datemath unit'); + } + + // normalize amount and unit (i.e. 120s -> 2m) + const { unit: normalizedUnit, amount: normalizedAmount } = normalizeDate(newAmount, newUnit); + + // How much have we changed the time? + const diffAmount = Math.abs(normalizedAmount - convertDate(parsedAmount, unit, normalizedUnit)); + // if `diffAmount` is not an integer after normalization, express the difference in the original unit + const shouldKeepDiffUnit = diffAmount % 1 !== 0; + + const nextValue = `now${operator}${normalizedAmount}${normalizedUnit}`; + + if (isDateInRange(nextValue)) { + return { + value: nextValue, + diffUnit: shouldKeepDiffUnit ? unit : newUnit, + diffAmount: shouldKeepDiffUnit ? Math.abs(newAmount - parsedAmount) : diffAmount, + }; + } else { + return undefined; + } +} + +function extendAbsoluteDatemath( + value: string, + direction: 'before' | 'after', + oppositeEdge: string +): DatemathExtension | undefined { + const valueTimestamp = datemathToEpochMillis(value)!; + const oppositeEdgeTimestamp = datemathToEpochMillis(oppositeEdge)!; + const actualTimestampDiff = Math.abs(valueTimestamp - oppositeEdgeTimestamp); + const normalizedDiff = normalizeDate(actualTimestampDiff, 'ms'); + const normalizedTimestampDiff = convertDate(normalizedDiff.amount, normalizedDiff.unit, 'ms'); + + const newValue = + direction === 'before' + ? valueTimestamp - normalizedTimestampDiff + : valueTimestamp + normalizedTimestampDiff; + + if (isDateInRange(newValue)) { + return { + value: new Date(newValue).toISOString(), + diffUnit: normalizedDiff.unit, + diffAmount: normalizedDiff.amount, + }; + } else { + return undefined; + } +} + +const CONVERSION_RATIOS: Record> = { + wy: [ + ['w', 52], // 1 year = 52 weeks + ['y', 1], + ], + w: [ + ['ms', 1000], + ['s', 60], + ['m', 60], + ['h', 24], + ['d', 7], // 1 week = 7 days + ['w', 4], // 1 month = 4 weeks = 28 days + ['M', 12], // 1 year = 12 months = 52 weeks = 364 days + ['y', 1], + ], + M: [ + ['ms', 1000], + ['s', 60], + ['m', 60], + ['h', 24], + ['d', 30], // 1 month = 30 days + ['M', 12], // 1 year = 12 months = 360 days + ['y', 1], + ], + default: [ + ['ms', 1000], + ['s', 60], + ['m', 60], + ['h', 24], + ['d', 365], // 1 year = 365 days + ['y', 1], + ], +}; + +function getRatioScale(from: Unit, to?: Unit) { + if ((from === 'y' && to === 'w') || (from === 'w' && to === 'y')) { + return CONVERSION_RATIOS.wy; + } else if (from === 'w' || to === 'w') { + return CONVERSION_RATIOS.w; + } else if (from === 'M' || to === 'M') { + return CONVERSION_RATIOS.M; + } else { + return CONVERSION_RATIOS.default; + } +} + +export function convertDate(value: number, from: Unit, to: Unit): number { + if (from === to) { + return value; + } + + const ratioScale = getRatioScale(from, to); + const fromIdx = ratioScale.findIndex((ratio) => ratio[0] === from); + const toIdx = ratioScale.findIndex((ratio) => ratio[0] === to); + + let convertedValue = value; + + if (fromIdx > toIdx) { + // `from` is the bigger unit. Multiply the value + for (let i = toIdx; i < fromIdx; i++) { + convertedValue *= ratioScale[i][1]; + } + } else { + // `from` is the smaller unit. Divide the value + for (let i = fromIdx; i < toIdx; i++) { + convertedValue /= ratioScale[i][1]; + } + } + + return convertedValue; +} + +export function normalizeDate(amount: number, unit: Unit): { amount: number; unit: Unit } { + // There is nothing after years + if (unit === 'y') { + return { amount, unit }; + } + + const nextUnit = dateMath.unitsAsc[dateMath.unitsAsc.indexOf(unit) + 1]; + const ratioScale = getRatioScale(unit, nextUnit); + const ratio = ratioScale.find((r) => r[0] === unit)![1]; + + const newAmount = amount / ratio; + + // Exact conversion + if (newAmount === 1) { + return { amount: newAmount, unit: nextUnit }; + } + + // Might be able to go one unit more, so try again, rounding the value + // 7200s => 120m => 2h + // 7249s ~> 120m ~> 2h + if (newAmount >= 2) { + return normalizeDate(Math.round(newAmount), nextUnit); + } + + // Cannot go one one unit above. Return as it is + return { amount, unit }; +} + +function isDateInRange(date: string | number): boolean { + try { + const epoch = typeof date === 'string' ? datemathToEpochMillis(date) ?? -1 : date; + return epoch >= 0 && epoch <= JS_MAX_DATE; + } catch { + return false; + } +} diff --git a/x-pack/plugins/logs_shared/public/utils/dev_mode.ts b/x-pack/plugins/logs_shared/public/utils/dev_mode.ts new file mode 100644 index 0000000000000..60571501b4193 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/dev_mode.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getReduxDevtools = () => (window as any).__REDUX_DEVTOOLS_EXTENSION__; + +export const hasReduxDevtools = () => getReduxDevtools() != null; + +export const isDevMode = () => process.env.NODE_ENV !== 'production'; diff --git a/x-pack/plugins/logs_shared/public/utils/handlers.ts b/x-pack/plugins/logs_shared/public/utils/handlers.ts new file mode 100644 index 0000000000000..f79e2fa4766a2 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/handlers.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEqual } from 'lodash'; + +export function callWithoutRepeats( + func: (...args: any[]) => T, + isArgsEqual: (firstArgs: any, secondArgs: any) => boolean = isEqual +) { + let previousArgs: any[]; + let previousResult: T; + + return (...args: any[]) => { + if (!isArgsEqual(args, previousArgs)) { + previousArgs = args; + previousResult = func(...args); + } + + return previousResult; + }; +} diff --git a/x-pack/plugins/logs_shared/public/utils/log_column_render_configuration.tsx b/x-pack/plugins/logs_shared/public/utils/log_column_render_configuration.tsx new file mode 100644 index 0000000000000..ff4a24f1498a6 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/log_column_render_configuration.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ReactNode } from 'react'; +import { JsonValue } from '@kbn/utility-types'; + +/** + * Interface for common configuration properties, regardless of the column type. + */ +interface CommonRenderConfiguration { + id: string; + width?: number | string; + header?: boolean | string; +} + +interface TimestampColumnRenderConfiguration { + timestampColumn: CommonRenderConfiguration & { + render?: (timestamp: number) => ReactNode; + }; +} + +interface MessageColumnRenderConfiguration { + messageColumn: CommonRenderConfiguration & { + render?: (message: string) => ReactNode; + }; +} + +interface FieldColumnRenderConfiguration { + fieldColumn: CommonRenderConfiguration & { + field: string; + render?: (value: JsonValue) => ReactNode; + }; +} + +export type LogColumnRenderConfiguration = + | TimestampColumnRenderConfiguration + | MessageColumnRenderConfiguration + | FieldColumnRenderConfiguration; + +export function isTimestampColumnRenderConfiguration( + column: LogColumnRenderConfiguration +): column is TimestampColumnRenderConfiguration { + return 'timestampColumn' in column; +} + +export function isMessageColumnRenderConfiguration( + column: LogColumnRenderConfiguration +): column is MessageColumnRenderConfiguration { + return 'messageColumn' in column; +} + +export function isFieldColumnRenderConfiguration( + column: LogColumnRenderConfiguration +): column is FieldColumnRenderConfiguration { + return 'fieldColumn' in column; +} + +export function columnWidthToCSS(width: number | string) { + return typeof width === 'number' ? `${width}px` : width; +} diff --git a/x-pack/plugins/infra/public/utils/log_entry/index.ts b/x-pack/plugins/logs_shared/public/utils/log_entry/index.ts similarity index 100% rename from x-pack/plugins/infra/public/utils/log_entry/index.ts rename to x-pack/plugins/logs_shared/public/utils/log_entry/index.ts diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts b/x-pack/plugins/logs_shared/public/utils/log_entry/log_entry.ts similarity index 100% rename from x-pack/plugins/infra/public/utils/log_entry/log_entry.ts rename to x-pack/plugins/logs_shared/public/utils/log_entry/log_entry.ts diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts b/x-pack/plugins/logs_shared/public/utils/log_entry/log_entry_highlight.ts similarity index 100% rename from x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts rename to x-pack/plugins/logs_shared/public/utils/log_entry/log_entry_highlight.ts diff --git a/x-pack/plugins/infra/public/utils/styles.ts b/x-pack/plugins/logs_shared/public/utils/styles.ts similarity index 100% rename from x-pack/plugins/infra/public/utils/styles.ts rename to x-pack/plugins/logs_shared/public/utils/styles.ts diff --git a/x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx b/x-pack/plugins/logs_shared/public/utils/typed_react.tsx similarity index 50% rename from x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx rename to x-pack/plugins/logs_shared/public/utils/typed_react.tsx index 064e944378cab..664894e1bf05c 100644 --- a/x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx +++ b/x-pack/plugins/logs_shared/public/utils/typed_react.tsx @@ -6,12 +6,6 @@ */ import React from 'react'; -import type { LogStreamProps } from './log_stream'; -const LazyLogStream = React.lazy(() => import('./log_stream')); - -export const LazyLogStreamWrapper = (props: LogStreamProps) => ( - }> - - -); +export type RendererResult = React.ReactElement | null; +export type RendererFunction = (args: RenderArgs) => Result; diff --git a/x-pack/plugins/logs_shared/public/utils/use_kibana_query_settings.ts b/x-pack/plugins/logs_shared/public/utils/use_kibana_query_settings.ts new file mode 100644 index 0000000000000..521cd0142303b --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/use_kibana_query_settings.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EsQueryConfig } from '@kbn/es-query'; +import { SerializableRecord } from '@kbn/utility-types'; +import { useMemo } from 'react'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; + +export const useKibanaQuerySettings = (): EsQueryConfig => { + const [allowLeadingWildcards] = useUiSetting$(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); + const [queryStringOptions] = useUiSetting$(UI_SETTINGS.QUERY_STRING_OPTIONS); + const [dateFormatTZ] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); + const [ignoreFilterIfFieldNotInIndex] = useUiSetting$( + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX + ); + + return useMemo( + () => ({ + allowLeadingWildcards, + queryStringOptions, + dateFormatTZ, + ignoreFilterIfFieldNotInIndex, + }), + [allowLeadingWildcards, dateFormatTZ, ignoreFilterIfFieldNotInIndex, queryStringOptions] + ); +}; diff --git a/x-pack/plugins/logs_shared/public/utils/use_kibana_ui_setting.ts b/x-pack/plugins/logs_shared/public/utils/use_kibana_ui_setting.ts new file mode 100644 index 0000000000000..e53e2b28cbdd4 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/use_kibana_ui_setting.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; + +/** + * This hook behaves like a `useState` hook in that it provides a requested + * setting value (with an optional default) from the Kibana UI settings (also + * known as "advanced settings") and a setter to change that setting: + * + * ``` + * const [quickRanges, setQuickRanges] = useKibanaUiSetting('timepicker:quickRanges'); + * ``` + * + * This is not just a static consumption of the value, but will reactively + * update when the underlying setting subscription of the `UiSettingsClient` + * notifies of a change. + * + * Unlike the `useState`, it doesn't give type guarantees for the value, + * because the underlying `UiSettingsClient` doesn't support that. + */ + +export interface TimePickerQuickRange { + from: string; + to: string; + display: string; +} + +export function useKibanaUiSetting( + key: 'timepicker:quickRanges', + defaultValue?: TimePickerQuickRange[] +): [ + TimePickerQuickRange[], + (key: 'timepicker:quickRanges', value: TimePickerQuickRange[]) => Promise +]; + +export function useKibanaUiSetting( + key: string, + defaultValue?: any +): [any, (key: string, value: any) => Promise]; + +export function useKibanaUiSetting(key: string, defaultValue?: any) { + const [uiSetting, setUiSetting] = useUiSetting$(key); + const uiSettingValue = useMemo(() => { + return uiSetting ? uiSetting : defaultValue; + }, [uiSetting, defaultValue]); + return [uiSettingValue, setUiSetting]; +} diff --git a/x-pack/plugins/logs_shared/public/utils/use_observable.ts b/x-pack/plugins/logs_shared/public/utils/use_observable.ts new file mode 100644 index 0000000000000..00efc4900b82f --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/use_observable.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useRef, useState } from 'react'; +import { + BehaviorSubject, + Observable, + OperatorFunction, + PartialObserver, + ReplaySubject, + Subscription, +} from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +export const useLatest = (value: Value) => { + const valueRef = useRef(value); + valueRef.current = value; + return valueRef; +}; + +export const useObservable = < + OutputValue, + OutputObservable extends Observable, + InputValues extends Readonly +>( + createObservableOnce: (inputValues: Observable) => OutputObservable, + inputValues: InputValues +) => { + const [output$, next] = useBehaviorSubject(createObservableOnce, () => inputValues); + + useEffect(() => { + next(inputValues); + // `inputValues` can't be statically analyzed + // eslint-disable-next-line react-hooks/exhaustive-deps + }, inputValues); + + return output$; +}; + +export const useBehaviorSubject = < + InputValue, + OutputValue, + OutputObservable extends Observable +>( + deriveObservableOnce: (input$: Observable) => OutputObservable, + createInitialValue: () => InputValue +) => { + const [[subject$, next], _] = useState(() => { + const newSubject$ = new BehaviorSubject(createInitialValue()); + const newNext = newSubject$.next.bind(newSubject$); + return [newSubject$, newNext] as const; + }); + const [output$] = useState(() => deriveObservableOnce(subject$)); + return [output$, next] as const; +}; + +export const useReplaySubject = < + InputValue, + OutputValue, + OutputObservable extends Observable +>( + deriveObservableOnce: (input$: Observable) => OutputObservable +) => { + const [[subject$, next], _] = useState(() => { + const newSubject$ = new ReplaySubject(); + const newNext = newSubject$.next.bind(newSubject$); + return [newSubject$, newNext] as const; + }); + const [output$] = useState(() => deriveObservableOnce(subject$)); + return [output$, next] as const; +}; + +export const useObservableState = ( + state$: Observable, + initialState: InitialState | (() => InitialState) +) => { + const [latestValue, setLatestValue] = useState(initialState); + const [latestError, setLatestError] = useState(); + + useSubscription(state$, { + next: setLatestValue, + error: setLatestError, + }); + + return { latestValue, latestError }; +}; + +export const useSubscription = ( + input$: Observable, + { next, error, complete, unsubscribe }: PartialObserver & { unsubscribe?: () => void } +) => { + const latestSubscription = useRef(); + const latestNext = useLatest(next); + const latestError = useLatest(error); + const latestComplete = useLatest(complete); + const latestUnsubscribe = useLatest(unsubscribe); + + useEffect(() => { + const fixedUnsubscribe = latestUnsubscribe.current; + + const subscription = input$.subscribe({ + next: (value) => { + return latestNext.current?.(value); + }, + error: (value) => latestError.current?.(value), + complete: () => latestComplete.current?.(), + }); + + latestSubscription.current = subscription; + + return () => { + subscription.unsubscribe(); + fixedUnsubscribe?.(); + }; + }, [input$, latestNext, latestError, latestComplete, latestUnsubscribe]); + + return latestSubscription.current; +}; + +export const useOperator = ( + input$: Observable, + operator: OperatorFunction +) => { + const latestOperator = useLatest(operator); + + return useObservable( + (inputs$) => + inputs$.pipe(switchMap(([currentInput$]) => latestOperator.current(currentInput$))), + [input$] as const + ); +}; + +export const tapUnsubscribe = + (onUnsubscribe: () => void) => + (source$: Observable) => { + return new Observable((subscriber) => { + const subscription = source$.subscribe({ + next: (value) => subscriber.next(value), + error: (error) => subscriber.error(error), + complete: () => subscriber.complete(), + }); + + return () => { + onUnsubscribe(); + subscription.unsubscribe(); + }; + }); + }; diff --git a/x-pack/plugins/logs_shared/public/utils/use_tracked_promise.ts b/x-pack/plugins/logs_shared/public/utils/use_tracked_promise.ts new file mode 100644 index 0000000000000..d12749ea69fdc --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/use_tracked_promise.ts @@ -0,0 +1,299 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 max-classes-per-file */ + +import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; + +interface UseTrackedPromiseArgs { + createPromise: (...args: Arguments) => Promise; + onResolve?: (result: Result) => void; + onReject?: (value: unknown) => void; + cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never'; + triggerOrThrow?: 'always' | 'whenMounted'; +} + +/** + * This hook manages a Promise factory and can create new Promises from it. The + * state of these Promises is tracked and they can be canceled when superseded + * to avoid race conditions. + * + * ``` + * const [requestState, performRequest] = useTrackedPromise( + * { + * cancelPreviousOn: 'resolution', + * createPromise: async (url: string) => { + * return await fetchSomething(url) + * }, + * onResolve: response => { + * setSomeState(response.data); + * }, + * onReject: response => { + * setSomeError(response); + * }, + * }, + * [fetchSomething] + * ); + * ``` + * + * The `onResolve` and `onReject` handlers are registered separately, because + * the hook will inject a rejection when in case of a canellation. The + * `cancelPreviousOn` attribute can be used to indicate when the preceding + * pending promises should be canceled: + * + * 'never': No preceding promises will be canceled. + * + * 'creation': Any preceding promises will be canceled as soon as a new one is + * created. + * + * 'settlement': Any preceding promise will be canceled when a newer promise is + * resolved or rejected. + * + * 'resolution': Any preceding promise will be canceled when a newer promise is + * resolved. + * + * 'rejection': Any preceding promise will be canceled when a newer promise is + * rejected. + * + * Any pending promises will be canceled when the component using the hook is + * unmounted, but their status will not be tracked to avoid React warnings + * about memory leaks. + * + * The last argument is a normal React hook dependency list that indicates + * under which conditions a new reference to the configuration object should be + * used. + * + * The `onResolve`, `onReject` and possible uncatched errors are only triggered + * if the underlying component is mounted. To ensure they always trigger (i.e. + * if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow` + * attribute: + * + * 'whenMounted': (default) they are called only if the component is mounted. + * + * 'always': they always call. The consumer is then responsible of ensuring no + * side effects happen if the underlying component is not mounted. + */ +export const useTrackedPromise = ( + { + createPromise, + onResolve = noOp, + onReject = noOp, + cancelPreviousOn = 'never', + triggerOrThrow = 'whenMounted', + }: UseTrackedPromiseArgs, + dependencies: DependencyList +) => { + const isComponentMounted = useMountedState(); + const shouldTriggerOrThrow = useCallback(() => { + switch (triggerOrThrow) { + case 'always': + return true; + case 'whenMounted': + return isComponentMounted(); + } + }, [isComponentMounted, triggerOrThrow]); + + /** + * If a promise is currently pending, this holds a reference to it and its + * cancellation function. + */ + const pendingPromises = useRef>>([]); + + /** + * The state of the promise most recently created by the `createPromise` + * factory. It could be uninitialized, pending, resolved or rejected. + */ + const [promiseState, setPromiseState] = useState>({ + state: 'uninitialized', + }); + + const reset = useCallback(() => { + setPromiseState({ + state: 'uninitialized', + }); + }, []); + + const execute = useMemo( + () => + (...args: Arguments) => { + let rejectCancellationPromise!: (value: any) => void; + const cancellationPromise = new Promise((_, reject) => { + rejectCancellationPromise = reject; + }); + + // remember the list of prior pending promises for cancellation + const previousPendingPromises = pendingPromises.current; + + const cancelPreviousPendingPromises = () => { + previousPendingPromises.forEach((promise) => promise.cancel()); + }; + + const newPromise = createPromise(...args); + const newCancelablePromise = Promise.race([newPromise, cancellationPromise]); + + // track this new state + setPromiseState({ + state: 'pending', + promise: newCancelablePromise, + }); + + if (cancelPreviousOn === 'creation') { + cancelPreviousPendingPromises(); + } + + const newPendingPromise: CancelablePromise = { + cancel: () => { + rejectCancellationPromise(new CanceledPromiseError()); + }, + cancelSilently: () => { + rejectCancellationPromise(new SilentCanceledPromiseError()); + }, + promise: newCancelablePromise.then( + (value) => { + if (['settlement', 'resolution'].includes(cancelPreviousOn)) { + cancelPreviousPendingPromises(); + } + + // remove itself from the list of pending promises + pendingPromises.current = pendingPromises.current.filter( + (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise + ); + + if (onResolve && shouldTriggerOrThrow()) { + onResolve(value); + } + + setPromiseState((previousPromiseState) => + previousPromiseState.state === 'pending' && + previousPromiseState.promise === newCancelablePromise + ? { + state: 'resolved', + promise: newPendingPromise.promise, + value, + } + : previousPromiseState + ); + + return value; + }, + (value) => { + if (!(value instanceof SilentCanceledPromiseError)) { + if (['settlement', 'rejection'].includes(cancelPreviousOn)) { + cancelPreviousPendingPromises(); + } + + // remove itself from the list of pending promises + pendingPromises.current = pendingPromises.current.filter( + (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise + ); + + if (shouldTriggerOrThrow()) { + if (onReject) { + onReject(value); + } else { + throw value; + } + } + + setPromiseState((previousPromiseState) => + previousPromiseState.state === 'pending' && + previousPromiseState.promise === newCancelablePromise + ? { + state: 'rejected', + promise: newCancelablePromise, + value, + } + : previousPromiseState + ); + } + } + ), + }; + + // add the new promise to the list of pending promises + pendingPromises.current = [...pendingPromises.current, newPendingPromise]; + + // silence "unhandled rejection" warnings + newPendingPromise.promise.catch(noOp); + + return newPendingPromise.promise; + }, + // the dependencies are managed by the caller + // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies + ); + + /** + * Cancel any pending promises silently to avoid memory leaks and race + * conditions. + */ + useEffect( + () => () => { + pendingPromises.current.forEach((promise) => promise.cancelSilently()); + }, + [] + ); + + return [promiseState, execute, reset] as [typeof promiseState, typeof execute, typeof reset]; +}; + +export interface UninitializedPromiseState { + state: 'uninitialized'; +} + +export interface PendingPromiseState { + state: 'pending'; + promise: Promise; +} + +export interface ResolvedPromiseState { + state: 'resolved'; + promise: Promise; + value: ResolvedValue; +} + +export interface RejectedPromiseState { + state: 'rejected'; + promise: Promise; + value: RejectedValue; +} + +export type SettledPromiseState = + | ResolvedPromiseState + | RejectedPromiseState; + +export type PromiseState = + | UninitializedPromiseState + | PendingPromiseState + | SettledPromiseState; + +export const isRejectedPromiseState = ( + promiseState: PromiseState +): promiseState is RejectedPromiseState => promiseState.state === 'rejected'; + +interface CancelablePromise { + // reject the promise prematurely with a CanceledPromiseError + cancel: () => void; + // reject the promise prematurely with a SilentCanceledPromiseError + cancelSilently: () => void; + // the tracked promise + promise: Promise; +} + +export class CanceledPromiseError extends Error { + public isCanceled = true; + + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class SilentCanceledPromiseError extends CanceledPromiseError {} + +const noOp = () => undefined; diff --git a/x-pack/plugins/logs_shared/public/utils/use_visibility_state.ts b/x-pack/plugins/logs_shared/public/utils/use_visibility_state.ts new file mode 100644 index 0000000000000..cff9a7190d2bb --- /dev/null +++ b/x-pack/plugins/logs_shared/public/utils/use_visibility_state.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 { useCallback, useMemo, useState } from 'react'; + +export const useVisibilityState = (initialState: boolean) => { + const [isVisible, setIsVisible] = useState(initialState); + + const hide = useCallback(() => setIsVisible(false), []); + const show = useCallback(() => setIsVisible(true), []); + const toggle = useCallback(() => setIsVisible((state) => !state), []); + + return useMemo( + () => ({ + hide, + isVisible, + show, + toggle, + }), + [hide, isVisible, show, toggle] + ); +}; diff --git a/x-pack/plugins/logs_shared/server/index.ts b/x-pack/plugins/logs_shared/server/index.ts new file mode 100644 index 0000000000000..95f6741bb54b8 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/index.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 { PluginInitializerContext } from '@kbn/core/server'; +import { LogsSharedPlugin } from './plugin'; + +export type { LogsSharedPluginSetup, LogsSharedPluginStart } from './types'; +export type { + LogsSharedLogEntriesDomain, + ILogsSharedLogEntriesDomain, +} from './lib/domains/log_entries_domain'; + +export { logViewSavedObjectName } from './saved_objects'; + +export function plugin(context: PluginInitializerContext) { + return new LogsSharedPlugin(context); +} diff --git a/x-pack/plugins/logs_shared/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/logs_shared/server/lib/adapters/framework/adapter_types.ts new file mode 100644 index 0000000000000..46e77f48d3f4b --- /dev/null +++ b/x-pack/plugins/logs_shared/server/lib/adapters/framework/adapter_types.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { JsonArray, JsonValue } from '@kbn/utility-types'; +import { RouteMethod } from '@kbn/core/server'; +import { VersionedRouteConfig } from '@kbn/core-http-server'; + +export interface CallWithRequestParams extends estypes.RequestBase { + max_concurrent_shard_requests?: number; + name?: string; + index?: string | string[]; + ignore_unavailable?: boolean; + allow_no_indices?: boolean; + size?: number; + terminate_after?: number; + fields?: estypes.Fields; + path?: string; + query?: string | object; + track_total_hits?: boolean | number; + body?: any; +} + +export interface LogsSharedDatabaseResponse { + took: number; + timeout: boolean; +} + +export interface LogsSharedDatabaseSearchResponse + extends LogsSharedDatabaseResponse { + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + timed_out: boolean; + aggregations?: Aggregations; + hits: { + total: { + value: number; + relation: string; + }; + hits: Hit[]; + }; +} + +export interface LogsSharedDatabaseMultiResponse + extends LogsSharedDatabaseResponse { + responses: Array>; +} + +export interface LogsSharedDatabaseGetIndicesAliasResponse { + [indexName: string]: { + aliases: { + [aliasName: string]: any; + }; + }; +} + +export interface LogsSharedDatabaseGetIndicesResponse { + [indexName: string]: { + aliases: { + [aliasName: string]: any; + }; + mappings: { + _meta: object; + dynamic_templates: any[]; + date_detection: boolean; + properties: { + [fieldName: string]: any; + }; + }; + settings: { index: object }; + }; +} + +export type SearchHit = estypes.SearchHit; + +export interface SortedSearchHit extends SearchHit { + sort: any[]; + _source: { + [field: string]: JsonValue; + }; + fields: { + [field: string]: JsonArray; + }; +} + +export type LogsSharedVersionedRouteConfig = { + method: RouteMethod; +} & VersionedRouteConfig; diff --git a/x-pack/plugins/logs_shared/server/lib/adapters/framework/index.ts b/x-pack/plugins/logs_shared/server/lib/adapters/framework/index.ts new file mode 100644 index 0000000000000..5d7c09c54b8c1 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/lib/adapters/framework/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './adapter_types'; diff --git a/x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..413ab3b333e82 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { TransportRequestParams } from '@elastic/elasticsearch'; +import { CoreSetup, IRouter, RouteMethod } from '@kbn/core/server'; +import { UI_SETTINGS } from '@kbn/data-plugin/server'; +import type { + LogsSharedPluginRequestHandlerContext, + LogsSharedServerPluginSetupDeps, + LogsSharedServerPluginStartDeps, +} from '../../../types'; +import { + CallWithRequestParams, + LogsSharedDatabaseGetIndicesAliasResponse, + LogsSharedDatabaseGetIndicesResponse, + LogsSharedDatabaseMultiResponse, + LogsSharedDatabaseSearchResponse, + LogsSharedVersionedRouteConfig, +} from './adapter_types'; + +interface FrozenIndexParams { + ignore_throttled?: boolean; +} + +export class KibanaFramework { + public router: IRouter; + public plugins: LogsSharedServerPluginSetupDeps; + + constructor( + core: CoreSetup, + plugins: LogsSharedServerPluginSetupDeps + ) { + this.router = core.http.createRouter(); + this.plugins = plugins; + } + + public registerVersionedRoute( + config: LogsSharedVersionedRouteConfig + ) { + const defaultOptions = { + tags: ['access:infra'], + }; + const routeConfig = { + access: config.access, + path: config.path, + // Currently we have no use of custom options beyond tags, this can be extended + // beyond defaultOptions if it's needed. + options: defaultOptions, + }; + switch (config.method) { + case 'get': + return this.router.versioned.get(routeConfig); + case 'post': + return this.router.versioned.post(routeConfig); + case 'delete': + return this.router.versioned.delete(routeConfig); + case 'put': + return this.router.versioned.put(routeConfig); + case 'patch': + return this.router.versioned.patch(routeConfig); + default: + throw new RangeError( + `#registerVersionedRoute: "${config.method}" is not an accepted method` + ); + } + } + + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + endpoint: 'search', + options?: CallWithRequestParams + ): Promise>; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + endpoint: 'msearch', + options?: CallWithRequestParams + ): Promise>; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + endpoint: 'indices.existsAlias', + options?: CallWithRequestParams + ): Promise; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + method: 'indices.getAlias', + options?: object + ): Promise; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + method: 'indices.get' | 'ml.getBuckets', + options?: object + ): Promise; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + method: 'transport.request', + options?: CallWithRequestParams + ): Promise; + callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + endpoint: string, + options?: CallWithRequestParams + ): Promise; + public async callWithRequest( + requestContext: LogsSharedPluginRequestHandlerContext, + endpoint: string, + params: CallWithRequestParams + ) { + const { elasticsearch, uiSettings } = await requestContext.core; + + const includeFrozen = await uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); + if (endpoint === 'msearch') { + const maxConcurrentShardRequests = await uiSettings.client.get( + UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS + ); + if (maxConcurrentShardRequests > 0) { + params = { ...params, max_concurrent_shard_requests: maxConcurrentShardRequests }; + } + } + + // Only set the "ignore_throttled" value (to false) if the Kibana setting + // for "search:includeFrozen" is true (i.e. don't ignore throttled indices, a triple negative!) + // More information: + // - https://github.com/elastic/kibana/issues/113197 + // - https://github.com/elastic/elasticsearch/pull/77479 + // + // NOTE: these params only need to be spread onto the search and msearch calls below + const frozenIndicesParams: FrozenIndexParams = {}; + if (includeFrozen) { + frozenIndicesParams.ignore_throttled = false; + } + + let apiResult; + switch (endpoint) { + case 'search': + apiResult = elasticsearch.client.asCurrentUser.search({ + ...params, + ...frozenIndicesParams, + }); + break; + case 'msearch': + apiResult = elasticsearch.client.asCurrentUser.msearch({ + ...params, + ...frozenIndicesParams, + } as estypes.MsearchRequest); + break; + case 'indices.existsAlias': + apiResult = elasticsearch.client.asCurrentUser.indices.existsAlias({ + ...params, + } as estypes.IndicesExistsAliasRequest); + break; + case 'indices.getAlias': + apiResult = elasticsearch.client.asCurrentUser.indices.getAlias({ + ...params, + }); + break; + case 'indices.get': + apiResult = elasticsearch.client.asCurrentUser.indices.get({ + ...params, + } as estypes.IndicesGetRequest); + break; + case 'transport.request': + apiResult = elasticsearch.client.asCurrentUser.transport.request({ + ...params, + } as TransportRequestParams); + break; + case 'ml.getBuckets': + apiResult = elasticsearch.client.asCurrentUser.ml.getBuckets({ + ...params, + } as estypes.MlGetBucketsRequest); + break; + } + return apiResult ? await apiResult : undefined; + } +} diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts similarity index 96% rename from x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts rename to x-pack/plugins/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 97ef51ded269d..66de5699cfb3e 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -12,7 +12,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import * as runtimeTypes from 'io-ts'; import { JsonArray } from '@kbn/utility-types'; import { compact } from 'lodash'; -import type { InfraPluginRequestHandlerContext } from '../../../types'; +import type { LogsSharedPluginRequestHandlerContext } from '../../../types'; import { LogEntriesAdapter, LogEntriesParams, @@ -28,11 +28,11 @@ import { TIMESTAMP_FIELD, TIEBREAKER_FIELD } from '../../../../common/constants' const TIMESTAMP_FORMAT = 'epoch_millis'; -export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { +export class LogsSharedKibanaLogEntriesAdapter implements LogEntriesAdapter { constructor(private readonly framework: KibanaFramework) {} public async getLogEntries( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, resolvedLogView: ResolvedLogView, fields: string[], params: LogEntriesParams @@ -123,7 +123,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } public async getContainedLogSummaryBuckets( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, resolvedLogView: ResolvedLogView, startTimestamp: number, endTimestamp: number, @@ -318,5 +318,3 @@ const LogSummaryResponseRuntimeType = runtimeTypes.type({ }), }), }); - -export type LogSummaryResponse = runtimeTypes.TypeOf; diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/index.ts b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/index.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/index.ts rename to x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/index.ts diff --git a/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.mock.ts b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.mock.ts new file mode 100644 index 0000000000000..74509f11ae4a7 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.mock.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ILogsSharedLogEntriesDomain } from './log_entries_domain'; + +export const createLogsSharedLogEntriesDomainMock = + (): jest.Mocked => { + return { + getLogEntriesAround: jest.fn(), + getLogEntries: jest.fn(), + getLogSummaryBucketsBetween: jest.fn(), + getLogSummaryHighlightBucketsBetween: jest.fn(), + getLogEntryDatasets: jest.fn(), + }; + }; diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts similarity index 84% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts rename to x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts index fcda9b30b0dac..92829c676b935 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -26,8 +26,8 @@ import { Fields, Highlights, } from '../../../services/log_entries/message/message'; -import type { InfraPluginRequestHandlerContext } from '../../../types'; -import { InfraBackendLibs } from '../../infra_types'; +import type { LogsSharedPluginRequestHandlerContext } from '../../../types'; +import { LogsSharedBackendLibs } from '../../logs_shared_types'; import { CompositeDatasetKey, createLogEntryDatasetsQuery, @@ -59,14 +59,54 @@ const FIELDS_FROM_CONTEXT = ['log.file.path', 'host.name', 'container.id'] as co const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; -export class InfraLogEntriesDomain { +export interface ILogsSharedLogEntriesDomain { + getLogEntriesAround( + requestContext: LogsSharedPluginRequestHandlerContext, + logView: LogViewReference, + params: LogEntriesAroundParams, + columnOverrides?: LogViewColumnConfiguration[] + ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }>; + getLogEntries( + requestContext: LogsSharedPluginRequestHandlerContext, + logView: LogViewReference, + params: LogEntriesParams, + columnOverrides?: LogViewColumnConfiguration[] + ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }>; + getLogSummaryBucketsBetween( + requestContext: LogsSharedPluginRequestHandlerContext, + logView: LogViewReference, + start: number, + end: number, + bucketSize: number, + filterQuery?: LogEntryQuery + ): Promise; + getLogSummaryHighlightBucketsBetween( + requestContext: LogsSharedPluginRequestHandlerContext, + logView: LogViewReference, + startTimestamp: number, + endTimestamp: number, + bucketSize: number, + highlightQueries: string[], + filterQuery?: LogEntryQuery + ): Promise; + getLogEntryDatasets( + requestContext: LogsSharedPluginRequestHandlerContext, + timestampField: string, + indexName: string, + startTime: number, + endTime: number, + runtimeMappings: estypes.MappingRuntimeFields + ): Promise; +} + +export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { constructor( private readonly adapter: LogEntriesAdapter, - private readonly libs: Pick + private readonly libs: Pick ) {} public async getLogEntriesAround( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, logView: LogViewReference, params: LogEntriesAroundParams, columnOverrides?: LogViewColumnConfiguration[] @@ -126,7 +166,7 @@ export class InfraLogEntriesDomain { } public async getLogEntries( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, logView: LogViewReference, params: LogEntriesParams, columnOverrides?: LogViewColumnConfiguration[] @@ -184,7 +224,7 @@ export class InfraLogEntriesDomain { } public async getLogSummaryBucketsBetween( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, logView: LogViewReference, start: number, end: number, @@ -208,7 +248,7 @@ export class InfraLogEntriesDomain { } public async getLogSummaryHighlightBucketsBetween( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, logView: LogViewReference, startTimestamp: number, endTimestamp: number, @@ -255,7 +295,7 @@ export class InfraLogEntriesDomain { } public async getLogEntryDatasets( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, timestampField: string, indexName: string, startTime: number, @@ -298,14 +338,14 @@ export class InfraLogEntriesDomain { export interface LogEntriesAdapter { getLogEntries( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, resolvedLogView: ResolvedLogView, fields: string[], params: LogEntriesParams ): Promise<{ documents: LogEntryDocument[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }>; getContainedLogSummaryBuckets( - requestContext: InfraPluginRequestHandlerContext, + requestContext: LogsSharedPluginRequestHandlerContext, resolvedLogView: ResolvedLogView, startTimestamp: number, endTimestamp: number, diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts similarity index 96% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts rename to x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts index a658c7deb317c..c2b966e86983f 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts +++ b/x-pack/plugins/logs_shared/server/lib/domains/log_entries_domain/queries/log_entry_datasets.ts @@ -99,5 +99,3 @@ export const logEntryDatasetsResponseRT = rt.intersection([ }), }), ]); - -export type LogEntryDatasetsResponse = rt.TypeOf; diff --git a/x-pack/plugins/logs_shared/server/lib/logs_shared_types.ts b/x-pack/plugins/logs_shared/server/lib/logs_shared_types.ts new file mode 100644 index 0000000000000..9d7ef50443c7f --- /dev/null +++ b/x-pack/plugins/logs_shared/server/lib/logs_shared_types.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 { Logger } from '@kbn/logging'; +import type { IBasePath } from '@kbn/core/server'; +import type { LogsSharedPluginStartServicesAccessor, UsageCollector } from '../types'; +import type { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; +import type { ILogsSharedLogEntriesDomain } from './domains/log_entries_domain'; + +export interface LogsSharedDomainLibs { + logEntries: ILogsSharedLogEntriesDomain; +} + +export interface LogsSharedBackendLibs extends LogsSharedDomainLibs { + basePath: IBasePath; + framework: KibanaFramework; + getStartServices: LogsSharedPluginStartServicesAccessor; + logger: Logger; + getUsageCollector: () => UsageCollector; +} diff --git a/x-pack/plugins/logs_shared/server/logs_shared_server.ts b/x-pack/plugins/logs_shared/server/logs_shared_server.ts new file mode 100644 index 0000000000000..60dc17be61d2d --- /dev/null +++ b/x-pack/plugins/logs_shared/server/logs_shared_server.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 { LogsSharedBackendLibs } from './lib/logs_shared_types'; +import { + initLogEntriesHighlightsRoute, + initLogEntriesSummaryHighlightsRoute, + initLogEntriesSummaryRoute, +} from './routes/log_entries'; +import { initLogViewRoutes } from './routes/log_views'; + +export const initLogsSharedServer = (libs: LogsSharedBackendLibs) => { + initLogEntriesHighlightsRoute(libs); + initLogEntriesSummaryRoute(libs); + initLogEntriesSummaryHighlightsRoute(libs); + initLogViewRoutes(libs); +}; diff --git a/x-pack/plugins/logs_shared/server/mocks.ts b/x-pack/plugins/logs_shared/server/mocks.ts new file mode 100644 index 0000000000000..a8b16381d32f3 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/mocks.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 { createLogsSharedLogEntriesDomainMock } from './lib/domains/log_entries_domain/log_entries_domain.mock'; +import { + createLogViewsServiceSetupMock, + createLogViewsServiceStartMock, +} from './services/log_views/log_views_service.mock'; +import { LogsSharedPluginSetup, LogsSharedPluginStart } from './types'; + +const createLogsSharedSetupMock = () => { + const logsSharedSetupMock: jest.Mocked = { + logViews: createLogViewsServiceSetupMock(), + logEntries: createLogsSharedLogEntriesDomainMock(), + registerUsageCollectorActions: jest.fn(), + }; + + return logsSharedSetupMock; +}; + +const createLogsSharedStartMock = () => { + const logsSharedStartMock: jest.Mocked = { + logViews: createLogViewsServiceStartMock(), + }; + return logsSharedStartMock; +}; + +export const logsSharedPluginMock = { + createSetupContract: createLogsSharedSetupMock, + createStartContract: createLogsSharedStartMock, +}; diff --git a/x-pack/plugins/logs_shared/server/plugin.ts b/x-pack/plugins/logs_shared/server/plugin.ts new file mode 100644 index 0000000000000..6ccf743dba7f2 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/plugin.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, CoreStart, Plugin, Logger } from '@kbn/core/server'; + +import { + LogsSharedPluginCoreSetup, + LogsSharedPluginSetup, + LogsSharedPluginStart, + LogsSharedServerPluginSetupDeps, + LogsSharedServerPluginStartDeps, + UsageCollector, +} from './types'; +import { logViewSavedObjectType } from './saved_objects'; +import { initLogsSharedServer } from './logs_shared_server'; +import { LogViewsService } from './services/log_views'; +import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; +import { LogsSharedBackendLibs, LogsSharedDomainLibs } from './lib/logs_shared_types'; +import { LogsSharedLogEntriesDomain } from './lib/domains/log_entries_domain'; +import { LogsSharedKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter'; +import { LogEntriesService } from './services/log_entries'; + +export class LogsSharedPlugin + implements + Plugin< + LogsSharedPluginSetup, + LogsSharedPluginStart, + LogsSharedServerPluginSetupDeps, + LogsSharedServerPluginStartDeps + > +{ + private readonly logger: Logger; + private libs!: LogsSharedBackendLibs; + private logViews: LogViewsService; + private usageCollector: UsageCollector; + + constructor(context: PluginInitializerContext) { + this.logger = context.logger.get(); + this.usageCollector = {}; + + this.logViews = new LogViewsService(this.logger.get('logViews')); + } + + public setup(core: LogsSharedPluginCoreSetup, plugins: LogsSharedServerPluginSetupDeps) { + const framework = new KibanaFramework(core, plugins); + + const logViews = this.logViews.setup(); + + // Register saved objects + core.savedObjects.registerType(logViewSavedObjectType); + + const domainLibs: LogsSharedDomainLibs = { + logEntries: new LogsSharedLogEntriesDomain(new LogsSharedKibanaLogEntriesAdapter(framework), { + framework, + getStartServices: () => core.getStartServices(), + }), + }; + + this.libs = { + ...domainLibs, + basePath: core.http.basePath, + framework, + getStartServices: () => core.getStartServices(), + logger: this.logger, + getUsageCollector: () => this.usageCollector, + }; + + // Register server side APIs + initLogsSharedServer(this.libs); + + const logEntriesService = new LogEntriesService(); + logEntriesService.setup(core, plugins); + + return { + ...domainLibs, + logViews, + registerUsageCollectorActions: (usageCollector: UsageCollector) => { + Object.assign(this.usageCollector, usageCollector); + }, + }; + } + + public start(core: CoreStart, plugins: LogsSharedServerPluginStartDeps) { + const logViews = this.logViews.start({ + savedObjects: core.savedObjects, + dataViews: plugins.dataViews, + elasticsearch: core.elasticsearch, + }); + + return { logViews }; + } + + public stop() {} +} diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/logs_shared/server/routes/log_entries/highlights.ts similarity index 96% rename from x-pack/plugins/infra/server/routes/log_entries/highlights.ts rename to x-pack/plugins/logs_shared/server/routes/log_entries/highlights.ts index 9d81e0d349f46..e8019c58d6000 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_entries/highlights.ts @@ -15,14 +15,14 @@ import { schema } from '@kbn/config-schema'; import { logEntriesV1 } from '../../../common/http_api'; import { throwErrors } from '../../../common/runtime_types'; -import { InfraBackendLibs } from '../../lib/infra_types'; +import { LogsSharedBackendLibs } from '../../lib/logs_shared_types'; import { parseFilterQuery } from '../../utils/serialized_query'; import { LogEntriesParams } from '../../lib/domains/log_entries_domain'; const escapeHatch = schema.object({}, { unknowns: 'allow' }); -export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBackendLibs) => { +export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: LogsSharedBackendLibs) => { framework .registerVersionedRoute({ access: 'internal', diff --git a/x-pack/plugins/infra/server/routes/log_entries/index.ts b/x-pack/plugins/logs_shared/server/routes/log_entries/index.ts similarity index 100% rename from x-pack/plugins/infra/server/routes/log_entries/index.ts rename to x-pack/plugins/logs_shared/server/routes/log_entries/index.ts diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary.ts b/x-pack/plugins/logs_shared/server/routes/log_entries/summary.ts similarity index 83% rename from x-pack/plugins/infra/server/routes/log_entries/summary.ts rename to x-pack/plugins/logs_shared/server/routes/log_entries/summary.ts index ee042f51d1dc8..2ac889ab9ffdf 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_entries/summary.ts @@ -15,14 +15,17 @@ import { schema } from '@kbn/config-schema'; import { logEntriesV1 } from '../../../common/http_api'; import { throwErrors } from '../../../common/runtime_types'; -import { InfraBackendLibs } from '../../lib/infra_types'; +import { LogsSharedBackendLibs } from '../../lib/logs_shared_types'; import { parseFilterQuery } from '../../utils/serialized_query'; -import { UsageCollector } from '../../usage/usage_collector'; const escapeHatch = schema.object({}, { unknowns: 'allow' }); -export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBackendLibs) => { +export const initLogEntriesSummaryRoute = ({ + framework, + logEntries, + getUsageCollector, +}: LogsSharedBackendLibs) => { framework .registerVersionedRoute({ access: 'internal', @@ -41,6 +44,8 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke ); const { logView, startTimestamp, endTimestamp, bucketSize, query } = payload; + const usageCollector = getUsageCollector(); + const buckets = await logEntries.getLogSummaryBucketsBetween( requestContext, logView, @@ -50,7 +55,9 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke parseFilterQuery(query) ); - UsageCollector.countLogs(); + if (typeof usageCollector.countLogs === 'function') { + usageCollector.countLogs(); + } return response.ok({ body: logEntriesV1.logEntriesSummaryResponseRT.encode({ diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts b/x-pack/plugins/logs_shared/server/routes/log_entries/summary_highlights.ts similarity index 95% rename from x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts rename to x-pack/plugins/logs_shared/server/routes/log_entries/summary_highlights.ts index e831ec51c4145..b4093f1d6543b 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_entries/summary_highlights.ts @@ -15,7 +15,7 @@ import { schema } from '@kbn/config-schema'; import { logEntriesV1 } from '../../../common/http_api'; import { throwErrors } from '../../../common/runtime_types'; -import { InfraBackendLibs } from '../../lib/infra_types'; +import { LogsSharedBackendLibs } from '../../lib/logs_shared_types'; import { parseFilterQuery } from '../../utils/serialized_query'; @@ -24,7 +24,7 @@ const escapeHatch = schema.object({}, { unknowns: 'allow' }); export const initLogEntriesSummaryHighlightsRoute = ({ framework, logEntries, -}: InfraBackendLibs) => { +}: LogsSharedBackendLibs) => { framework .registerVersionedRoute({ access: 'internal', diff --git a/x-pack/plugins/infra/server/routes/log_views/get_log_view.ts b/x-pack/plugins/logs_shared/server/routes/log_views/get_log_view.ts similarity index 92% rename from x-pack/plugins/infra/server/routes/log_views/get_log_view.ts rename to x-pack/plugins/logs_shared/server/routes/log_views/get_log_view.ts index 0c3cb7cbac2af..ef6e69f07d0ef 100644 --- a/x-pack/plugins/infra/server/routes/log_views/get_log_view.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_views/get_log_view.ts @@ -9,14 +9,14 @@ import { logViewsV1 } from '../../../common/http_api'; import { LOG_VIEW_URL } from '../../../common/http_api/log_views'; import { createValidationFunction } from '../../../common/runtime_types'; import type { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter'; -import type { InfraPluginStartServicesAccessor } from '../../types'; +import type { LogsSharedPluginStartServicesAccessor } from '../../types'; export const initGetLogViewRoute = ({ framework, getStartServices, }: { framework: KibanaFramework; - getStartServices: InfraPluginStartServicesAccessor; + getStartServices: LogsSharedPluginStartServicesAccessor; }) => { framework .registerVersionedRoute({ diff --git a/x-pack/plugins/infra/server/routes/log_views/index.ts b/x-pack/plugins/logs_shared/server/routes/log_views/index.ts similarity index 82% rename from x-pack/plugins/infra/server/routes/log_views/index.ts rename to x-pack/plugins/logs_shared/server/routes/log_views/index.ts index fa7e6f6e1b9d3..b2670cfa3e40a 100644 --- a/x-pack/plugins/infra/server/routes/log_views/index.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_views/index.ts @@ -6,13 +6,13 @@ */ import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter'; -import { InfraPluginStartServicesAccessor } from '../../types'; +import { LogsSharedPluginStartServicesAccessor } from '../../types'; import { initGetLogViewRoute } from './get_log_view'; import { initPutLogViewRoute } from './put_log_view'; export const initLogViewRoutes = (dependencies: { framework: KibanaFramework; - getStartServices: InfraPluginStartServicesAccessor; + getStartServices: LogsSharedPluginStartServicesAccessor; }) => { initGetLogViewRoute(dependencies); initPutLogViewRoute(dependencies); diff --git a/x-pack/plugins/infra/server/routes/log_views/put_log_view.ts b/x-pack/plugins/logs_shared/server/routes/log_views/put_log_view.ts similarity index 93% rename from x-pack/plugins/infra/server/routes/log_views/put_log_view.ts rename to x-pack/plugins/logs_shared/server/routes/log_views/put_log_view.ts index 310960156abfc..899200902aa1e 100644 --- a/x-pack/plugins/infra/server/routes/log_views/put_log_view.ts +++ b/x-pack/plugins/logs_shared/server/routes/log_views/put_log_view.ts @@ -9,14 +9,14 @@ import { logViewsV1 } from '../../../common/http_api'; import { LOG_VIEW_URL } from '../../../common/http_api/log_views'; import { createValidationFunction } from '../../../common/runtime_types'; import type { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter'; -import type { InfraPluginStartServicesAccessor } from '../../types'; +import type { LogsSharedPluginStartServicesAccessor } from '../../types'; export const initPutLogViewRoute = ({ framework, getStartServices, }: { framework: KibanaFramework; - getStartServices: InfraPluginStartServicesAccessor; + getStartServices: LogsSharedPluginStartServicesAccessor; }) => { framework .registerVersionedRoute({ diff --git a/x-pack/plugins/logs_shared/server/saved_objects/index.ts b/x-pack/plugins/logs_shared/server/saved_objects/index.ts new file mode 100644 index 0000000000000..bd7ecac5179a1 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/saved_objects/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './log_view'; diff --git a/x-pack/plugins/infra/server/saved_objects/log_view/index.ts b/x-pack/plugins/logs_shared/server/saved_objects/log_view/index.ts similarity index 100% rename from x-pack/plugins/infra/server/saved_objects/log_view/index.ts rename to x-pack/plugins/logs_shared/server/saved_objects/log_view/index.ts diff --git a/x-pack/plugins/infra/server/saved_objects/log_view/log_view_saved_object.ts b/x-pack/plugins/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts similarity index 100% rename from x-pack/plugins/infra/server/saved_objects/log_view/log_view_saved_object.ts rename to x-pack/plugins/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts index 9202227867dca..246c398ea5a65 100644 --- a/x-pack/plugins/infra/server/saved_objects/log_view/log_view_saved_object.ts +++ b/x-pack/plugins/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { SavedObject, SavedObjectsType } from '@kbn/core/server'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { SavedObject, SavedObjectsType } from '@kbn/core/server'; import { logViewSavedObjectRT } from './types'; export const logViewSavedObjectName = 'infrastructure-monitoring-log-view'; diff --git a/x-pack/plugins/infra/server/saved_objects/log_view/references/index.ts b/x-pack/plugins/logs_shared/server/saved_objects/log_view/references/index.ts similarity index 100% rename from x-pack/plugins/infra/server/saved_objects/log_view/references/index.ts rename to x-pack/plugins/logs_shared/server/saved_objects/log_view/references/index.ts diff --git a/x-pack/plugins/infra/server/saved_objects/log_view/references/log_indices.ts b/x-pack/plugins/logs_shared/server/saved_objects/log_view/references/log_indices.ts similarity index 85% rename from x-pack/plugins/infra/server/saved_objects/log_view/references/log_indices.ts rename to x-pack/plugins/logs_shared/server/saved_objects/log_view/references/log_indices.ts index ea45be30bc0b7..660f01f47eb5e 100644 --- a/x-pack/plugins/infra/server/saved_objects/log_view/references/log_indices.ts +++ b/x-pack/plugins/logs_shared/server/saved_objects/log_view/references/log_indices.ts @@ -7,24 +7,24 @@ import { SavedObjectReference } from '@kbn/core/server'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; -import { LogViewAttributes } from '../../../../common/log_views'; import { SavedObjectAttributesWithReferences, SavedObjectReferenceResolutionError, } from '../../references'; +import { LogViewSavedObjectAttributes } from '../types'; export const logIndicesDataViewReferenceName = 'log-indices-data-view-0'; export const extractLogIndicesSavedObjectReferences = ( - unextractedAttributes: LogViewAttributes -): SavedObjectAttributesWithReferences => { + unextractedAttributes: LogViewSavedObjectAttributes +): SavedObjectAttributesWithReferences => { if (unextractedAttributes.logIndices.type === 'data_view') { const logDataViewReference: SavedObjectReference = { id: unextractedAttributes.logIndices.dataViewId, type: DATA_VIEW_SAVED_OBJECT_TYPE, name: logIndicesDataViewReferenceName, }; - const attributes: LogViewAttributes = { + const attributes: LogViewSavedObjectAttributes = { ...unextractedAttributes, logIndices: { ...unextractedAttributes.logIndices, @@ -44,9 +44,9 @@ export const extractLogIndicesSavedObjectReferences = ( }; export const resolveLogIndicesSavedObjectReferences = ( - attributes: LogViewAttributes, + attributes: LogViewSavedObjectAttributes, references: SavedObjectReference[] -): LogViewAttributes => { +): LogViewSavedObjectAttributes => { if (attributes.logIndices?.type === 'data_view') { const logDataViewReference = references.find( (reference) => reference.name === logIndicesDataViewReferenceName diff --git a/x-pack/plugins/infra/server/saved_objects/log_view/types.ts b/x-pack/plugins/logs_shared/server/saved_objects/log_view/types.ts similarity index 95% rename from x-pack/plugins/infra/server/saved_objects/log_view/types.ts rename to x-pack/plugins/logs_shared/server/saved_objects/log_view/types.ts index 34387b5e33085..fb8bf49781a2d 100644 --- a/x-pack/plugins/infra/server/saved_objects/log_view/types.ts +++ b/x-pack/plugins/logs_shared/server/saved_objects/log_view/types.ts @@ -58,6 +58,8 @@ export const logViewSavedObjectAttributesRT = rt.strict({ logColumns: rt.array(logViewSavedObjectColumnConfigurationRT), }); +export type LogViewSavedObjectAttributes = rt.TypeOf; + export const logViewSavedObjectRT = rt.intersection([ rt.type({ id: rt.string, diff --git a/x-pack/plugins/logs_shared/server/saved_objects/references.test.ts b/x-pack/plugins/logs_shared/server/saved_objects/references.test.ts new file mode 100644 index 0000000000000..674aabbd9d058 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/saved_objects/references.test.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectReference } from '@kbn/core/server'; +import { + extractSavedObjectReferences, + resolveSavedObjectReferences, + SavedObjectAttributesWithReferences, +} from './references'; + +it('extractSavedObjectReferences extracts references using the given extractors', () => { + const { attributes, references } = extractSavedObjectReferences([ + extractReferenceA, + extractReferenceB, + ])({ + a: 'id-a', + b: 'id-b', + c: 'something-else', + }); + + expect(references).toMatchObject([ + { id: 'id-a', name: REFERENCE_A_NAME, type: 'some-reference' }, + { id: 'id-b', name: REFERENCE_B_NAME, type: 'some-reference' }, + ]); + expect(attributes).toMatchObject({ + a: REFERENCE_A_NAME, + b: REFERENCE_B_NAME, + c: 'something-else', + }); +}); + +it('resolveSavedObjectReferences resolves references using the given resolvers', () => { + const attributes = resolveSavedObjectReferences([resolveReferenceA, resolveReferenceB])( + { + a: REFERENCE_A_NAME, + b: REFERENCE_B_NAME, + c: 'something-else', + }, + [ + { id: 'id-a', name: REFERENCE_A_NAME, type: 'some-reference' }, + { id: 'id-b', name: REFERENCE_B_NAME, type: 'some-reference' }, + ] + ); + + expect(attributes).toMatchObject({ + a: 'id-a', + b: 'id-b', + c: 'something-else', + }); +}); + +interface TestSavedObjectAttributes { + a: string; + b: string; + c: string; +} + +const REFERENCE_A_NAME = 'reference-a'; +const REFERENCE_B_NAME = 'reference-b'; + +const extractReferenceA = ( + attributes: TestSavedObjectAttributes +): SavedObjectAttributesWithReferences => ({ + attributes: { ...attributes, a: REFERENCE_A_NAME }, + references: [ + { + id: attributes.a, + name: REFERENCE_A_NAME, + type: 'some-reference', + }, + ], +}); + +const extractReferenceB = ( + attributes: TestSavedObjectAttributes +): SavedObjectAttributesWithReferences => ({ + attributes: { ...attributes, b: REFERENCE_B_NAME }, + references: [ + { + id: attributes.b, + name: REFERENCE_B_NAME, + type: 'some-reference', + }, + ], +}); + +const resolveReferenceA = ( + attributes: TestSavedObjectAttributes, + references: SavedObjectReference[] +): TestSavedObjectAttributes => { + const referenceA = references.find((reference) => reference.name === REFERENCE_A_NAME); + + if (referenceA != null) { + return { + ...attributes, + a: referenceA.id, + }; + } else { + return attributes; + } +}; + +const resolveReferenceB = ( + attributes: TestSavedObjectAttributes, + references: SavedObjectReference[] +): TestSavedObjectAttributes => { + const referenceB = references.find((reference) => reference.name === REFERENCE_B_NAME); + + if (referenceB != null) { + return { + ...attributes, + b: referenceB.id, + }; + } else { + return attributes; + } +}; diff --git a/x-pack/plugins/logs_shared/server/saved_objects/references.ts b/x-pack/plugins/logs_shared/server/saved_objects/references.ts new file mode 100644 index 0000000000000..13b64ab6e6e73 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/saved_objects/references.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { SavedObject, SavedObjectReference } from '@kbn/core/server'; + +export type SavedObjectAttributesWithReferences = Pick< + SavedObject, + 'attributes' | 'references' +>; + +export type SavedObjectReferenceExtractor = ( + savedObjectAttributes: SavedObjectAttributes +) => SavedObjectAttributesWithReferences; + +export type SavedObjectReferenceResolver = ( + savedObjectAttributes: SavedObjectAttributes, + references: SavedObjectReference[] +) => SavedObjectAttributes; + +export const savedObjectReferenceRT = rt.strict({ + name: rt.string, + type: rt.string, + id: rt.string, +}); + +/** + * Rewrites a saved object such that well-known saved object references + * are extracted in the `references` array and replaced by the appropriate + * name. This is the inverse operation to `resolveSavedObjectReferences`. + */ +export const extractSavedObjectReferences = + ( + referenceExtractors: Array> + ) => + ( + savedObjectAttributes: SavedObjectAttributes + ): SavedObjectAttributesWithReferences => + referenceExtractors.reduce>( + ({ attributes: accumulatedAttributes, references: accumulatedReferences }, extract) => { + const { attributes, references } = extract(accumulatedAttributes); + return { + attributes, + references: [...accumulatedReferences, ...references], + }; + }, + { + attributes: savedObjectAttributes, + references: [], + } + ); + +/** + * Rewrites a source configuration such that well-known saved object references + * are resolved from the `references` argument and replaced by the real saved + * object ids. This is the inverse operation to `extractSavedObjectReferences`. + */ +export const resolveSavedObjectReferences = + ( + referenceResolvers: Array> + ) => + (attributes: SavedObjectAttributes, references: SavedObjectReference[]): SavedObjectAttributes => + referenceResolvers.reduce( + (accumulatedAttributes, resolve) => resolve(accumulatedAttributes, references), + attributes + ); + +export class SavedObjectReferenceResolutionError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = 'SavedObjectReferenceResolutionError'; + } +} diff --git a/x-pack/plugins/infra/server/services/log_entries/index.ts b/x-pack/plugins/logs_shared/server/services/log_entries/index.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/index.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/index.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_service.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/log_entries_service.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entry_search_strategy.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/log_entry_search_strategy.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_iis.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_iis.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_redis.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_redis.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_redis.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_redis.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_system.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_system.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_system.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_system.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic.test.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic.test.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic_webserver.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic_webserver.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic_webserver.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/generic_webserver.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/helpers.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/helpers.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/helpers.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/helpers.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/index.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/index.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/index.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/builtin_rules/index.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/index.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/index.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/index.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/index.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/message.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/message.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/message.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/message.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts b/x-pack/plugins/logs_shared/server/services/log_entries/message/rule_types.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/message/rule_types.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/common.ts b/x-pack/plugins/logs_shared/server/services/log_entries/queries/common.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/queries/common.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/queries/common.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts b/x-pack/plugins/logs_shared/server/services/log_entries/queries/log_entries.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/queries/log_entries.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts b/x-pack/plugins/logs_shared/server/services/log_entries/queries/log_entry.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/queries/log_entry.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/types.ts b/x-pack/plugins/logs_shared/server/services/log_entries/types.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_entries/types.ts rename to x-pack/plugins/logs_shared/server/services/log_entries/types.ts diff --git a/x-pack/plugins/logs_shared/server/services/log_views/errors.ts b/x-pack/plugins/logs_shared/server/services/log_views/errors.ts new file mode 100644 index 0000000000000..088dd6244bba6 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/services/log_views/errors.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. + */ + +/* eslint-disable max-classes-per-file */ + +export class NotFoundError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class LogViewFallbackUnregisteredError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/x-pack/plugins/infra/server/services/log_views/index.ts b/x-pack/plugins/logs_shared/server/services/log_views/index.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_views/index.ts rename to x-pack/plugins/logs_shared/server/services/log_views/index.ts diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.mock.ts similarity index 100% rename from x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts rename to x-pack/plugins/logs_shared/server/services/log_views/log_views_client.mock.ts diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.test.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts similarity index 88% rename from x-pack/plugins/infra/server/services/log_views/log_views_client.test.ts rename to x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts index e517ae8aef7f0..5efdf9e125deb 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.test.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts @@ -17,42 +17,11 @@ import { LogViewsStaticConfig, } from '../../../common/log_views'; import { createLogViewMock } from '../../../common/log_views/log_view.mock'; -import { InfraSource } from '../../lib/sources'; -import { createInfraSourcesMock } from '../../lib/sources/mocks'; import { extractLogViewSavedObjectReferences, logViewSavedObjectName, } from '../../saved_objects/log_view'; -import { getAttributesFromSourceConfiguration, LogViewsClient } from './log_views_client'; - -describe('getAttributesFromSourceConfiguration function', () => { - it('converts the index_pattern log indices type to data_view', () => { - const logViewAttributes = getAttributesFromSourceConfiguration(basicTestSourceConfiguration); - - expect(logViewAttributes.logIndices).toEqual({ - type: 'data_view', - dataViewId: 'INDEX_PATTERN_ID', - }); - }); - - it('preserves the index_name log indices type', () => { - const logViewAttributes = getAttributesFromSourceConfiguration({ - ...basicTestSourceConfiguration, - configuration: { - ...basicTestSourceConfiguration.configuration, - logIndices: { - type: 'index_name', - indexName: 'INDEX_NAME', - }, - }, - }); - - expect(logViewAttributes.logIndices).toEqual({ - type: 'index_name', - indexName: 'INDEX_NAME', - }); - }); -}); +import { LogViewsClient } from './log_views_client'; describe('LogViewsClient class', () => { it('getLogView resolves the default id to a real saved object id if it exists', async () => { @@ -116,9 +85,9 @@ describe('LogViewsClient class', () => { }); it('getLogView preserves the default id for fallback lookups', async () => { - const { infraSources, logViewsClient, savedObjectsClient } = createLogViewsClient(); + const { logViewFallbackHandler, logViewsClient, savedObjectsClient } = createLogViewsClient(); - infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + logViewFallbackHandler.mockResolvedValue(basicTestSourceConfiguration); savedObjectsClient.find.mockResolvedValue({ total: 0, @@ -129,10 +98,9 @@ describe('LogViewsClient class', () => { await logViewsClient.getLogView(defaultLogViewId); - expect(infraSources.getSourceConfiguration).toHaveBeenCalledWith( - savedObjectsClient, - defaultLogViewId - ); + expect(logViewFallbackHandler).toHaveBeenCalledWith(defaultLogViewId, { + soClient: savedObjectsClient, + }); }); it('putLogView resolves the default id to a real saved object id if one exists', async () => { @@ -364,7 +332,7 @@ const createLogViewsClient = () => { const logger = loggerMock.create(); const dataViews = dataViewsServiceMock; const savedObjectsClient = savedObjectsClientMock.create(); - const infraSources = createInfraSourcesMock(); + const logViewFallbackHandler = jest.fn(); const internalLogViews = new Map(); const logViewStaticConfig: LogViewsStaticConfig = { messageFields: ['message'], @@ -374,14 +342,14 @@ const createLogViewsClient = () => { logger, Promise.resolve(dataViews), savedObjectsClient, - infraSources, + logViewFallbackHandler, internalLogViews, logViewStaticConfig ); return { dataViews, - infraSources, + logViewFallbackHandler, internalLogViews, logViewStaticConfig, logViewsClient, @@ -389,7 +357,7 @@ const createLogViewsClient = () => { }; }; -const basicTestSourceConfiguration: InfraSource = { +const basicTestSourceConfiguration = { id: 'ID', origin: 'stored', configuration: { diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.ts similarity index 77% rename from x-pack/plugins/infra/server/services/log_views/log_views_client.ts rename to x-pack/plugins/logs_shared/server/services/log_views/log_views_client.ts index 3f832c6770717..452bc1b3b969e 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.ts @@ -16,7 +16,6 @@ import { import { defaultLogViewAttributes, defaultLogViewId, - LogIndexReference, LogView, LogViewAttributes, LogViewReference, @@ -26,16 +25,14 @@ import { resolveLogView, } from '../../../common/log_views'; import { decodeOrThrow } from '../../../common/runtime_types'; -import { LogIndexReference as SourceConfigurationLogIndexReference } from '../../../common/source_configuration/source_configuration'; -import type { IInfraSources, InfraSource } from '../../lib/sources'; import { extractLogViewSavedObjectReferences, logViewSavedObjectName, resolveLogViewSavedObjectReferences, } from '../../saved_objects/log_view'; import { logViewSavedObjectRT } from '../../saved_objects/log_view/types'; -import { NotFoundError } from './errors'; -import { ILogViewsClient } from './types'; +import { LogViewFallbackUnregisteredError, NotFoundError } from './errors'; +import { ILogViewsClient, LogViewFallbackHandler } from './types'; type DataViewsService = ReturnType; @@ -48,7 +45,7 @@ export class LogViewsClient implements ILogViewsClient { private readonly logger: Logger, private readonly dataViews: DataViewsService, private readonly savedObjectsClient: SavedObjectsClientContract, - private readonly infraSources: IInfraSources, + private readonly logViewFallbackHandler: LogViewFallbackHandler, private readonly internalLogViews: Map, private readonly config: LogViewsStaticConfig ) {} @@ -62,7 +59,7 @@ export class LogViewsClient implements ILogViewsClient { ) .catch((err) => err instanceof NotFoundError - ? this.getLogViewFromInfraSourceConfiguration(logViewId) + ? this.getLogViewFromLogsSharedSourceConfiguration(logViewId) : Promise.reject(err) ); } @@ -142,21 +139,16 @@ export class LogViewsClient implements ILogViewsClient { return internalLogView; } - private async getLogViewFromInfraSourceConfiguration(sourceId: string): Promise { - this.logger.debug(`Trying to load log view from source configuration "${sourceId}"...`); + private async getLogViewFromLogsSharedSourceConfiguration(sourceId: string): Promise { + this.logger.debug(`Trying to load log view from fallback configuration "${sourceId}"...`); - const sourceConfiguration = await this.infraSources.getSourceConfiguration( - this.savedObjectsClient, - sourceId - ); + if (this.logViewFallbackHandler === null) { + throw new LogViewFallbackUnregisteredError( + 'A fallback LogView handler is not registered. Register one in the setup method of your server plugin.' + ); + } - return { - id: sourceConfiguration.id, - version: sourceConfiguration.version, - updatedAt: sourceConfiguration.updatedAt, - origin: `infra-source-${sourceConfiguration.origin}`, - attributes: getAttributesFromSourceConfiguration(sourceConfiguration), - }; + return this.logViewFallbackHandler(sourceId, { soClient: this.savedObjectsClient }); } private async resolveLogViewId(logViewId: string): Promise { @@ -197,22 +189,3 @@ const getLogViewFromSavedObject = (savedObject: SavedObject): LogView = ), }; }; - -export const getAttributesFromSourceConfiguration = ({ - configuration: { name, description, logIndices, logColumns }, -}: InfraSource): LogViewAttributes => ({ - name, - description, - logIndices: getLogIndicesFromSourceConfigurationLogIndices(logIndices), - logColumns, -}); - -const getLogIndicesFromSourceConfigurationLogIndices = ( - logIndices: SourceConfigurationLogIndexReference -): LogIndexReference => - logIndices.type === 'index_pattern' - ? { - type: 'data_view', - dataViewId: logIndices.indexPatternId, - } - : logIndices; diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_service.mock.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_service.mock.ts similarity index 90% rename from x-pack/plugins/infra/server/services/log_views/log_views_service.mock.ts rename to x-pack/plugins/logs_shared/server/services/log_views/log_views_service.mock.ts index e472e30fae2b4..295b1fd77452f 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_service.mock.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_service.mock.ts @@ -10,6 +10,8 @@ import { LogViewsServiceSetup, LogViewsServiceStart } from './types'; export const createLogViewsServiceSetupMock = (): jest.Mocked => ({ defineInternalLogView: jest.fn(), + registerLogViewFallbackHandler: jest.fn(), + setLogViewsStaticConfig: jest.fn(), }); export const createLogViewsServiceStartMock = (): jest.Mocked => ({ diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_service.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_service.ts similarity index 65% rename from x-pack/plugins/infra/server/services/log_views/log_views_service.ts rename to x-pack/plugins/logs_shared/server/services/log_views/log_views_service.ts index cf1c7595ae5cb..5479c16dff411 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_service.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_service.ts @@ -11,12 +11,25 @@ import { Logger, SavedObjectsClientContract, } from '@kbn/core/server'; -import { defaultLogViewAttributes, LogView, LogViewAttributes } from '../../../common/log_views'; +import { + defaultLogViewAttributes, + defaultLogViewsStaticConfig, + LogView, + LogViewAttributes, + LogViewsStaticConfig, +} from '../../../common/log_views'; import { LogViewsClient } from './log_views_client'; -import { LogViewsServiceSetup, LogViewsServiceStart, LogViewsServiceStartDeps } from './types'; +import { + LogViewFallbackHandler, + LogViewsServiceSetup, + LogViewsServiceStart, + LogViewsServiceStartDeps, +} from './types'; export class LogViewsService { private internalLogViews: Map = new Map(); + private logViewFallbackHandler: LogViewFallbackHandler | null = null; + private logViewsStaticConfig: LogViewsStaticConfig = defaultLogViewsStaticConfig; constructor(private readonly logger: Logger) {} @@ -24,7 +37,7 @@ export class LogViewsService { const { internalLogViews } = this; return { - defineInternalLogView(logViewId: string, logViewAttributes: Partial) { + defineInternalLogView: (logViewId: string, logViewAttributes: Partial) => { internalLogViews.set(logViewId, { id: logViewId, origin: 'internal', @@ -32,17 +45,21 @@ export class LogViewsService { updatedAt: Date.now(), }); }, + registerLogViewFallbackHandler: (handler) => { + this.logViewFallbackHandler = handler; + }, + setLogViewsStaticConfig: (config: LogViewsStaticConfig) => { + this.logViewsStaticConfig = config; + }, }; } public start({ - config, dataViews, elasticsearch, - infraSources, savedObjects, }: LogViewsServiceStartDeps): LogViewsServiceStart { - const { internalLogViews, logger } = this; + const { internalLogViews, logger, logViewFallbackHandler, logViewsStaticConfig } = this; return { getClient( @@ -54,9 +71,9 @@ export class LogViewsService { logger, dataViews.dataViewsServiceFactory(savedObjectsClient, elasticsearchClient, request), savedObjectsClient, - infraSources, + logViewFallbackHandler, internalLogViews, - config + logViewsStaticConfig ); }, getScopedClient(request: KibanaRequest) { diff --git a/x-pack/plugins/infra/server/services/log_views/types.ts b/x-pack/plugins/logs_shared/server/services/log_views/types.ts similarity index 81% rename from x-pack/plugins/infra/server/services/log_views/types.ts rename to x-pack/plugins/logs_shared/server/services/log_views/types.ts index b5f91cb3587b4..db1410207c11a 100644 --- a/x-pack/plugins/infra/server/services/log_views/types.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/types.ts @@ -20,18 +20,25 @@ import { LogViewsStaticConfig, ResolvedLogView, } from '../../../common/log_views'; -import { InfraSources } from '../../lib/sources'; export interface LogViewsServiceStartDeps { - config: LogViewsStaticConfig; dataViews: DataViewsServerPluginStart; elasticsearch: ElasticsearchServiceStart; - infraSources: InfraSources; savedObjects: SavedObjectsServiceStart; } +export interface LogViewFallbackHandlerOptions { + soClient: SavedObjectsClientContract; +} + +export type LogViewFallbackHandler = + | ((sourceId: string, options: LogViewFallbackHandlerOptions) => Promise) + | null; + export interface LogViewsServiceSetup { defineInternalLogView(logViewId: string, logViewAttributes: Partial): void; + registerLogViewFallbackHandler: (handler: LogViewFallbackHandler) => void; + setLogViewsStaticConfig: (config: LogViewsStaticConfig) => void; } export interface LogViewsServiceStart { diff --git a/x-pack/plugins/logs_shared/server/types.ts b/x-pack/plugins/logs_shared/server/types.ts new file mode 100644 index 0000000000000..2e922eceeb183 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/types.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 { CoreSetup, RequestHandlerContext } from '@kbn/core/server'; +import { + PluginSetup as DataPluginSetup, + PluginStart as DataPluginStart, +} from '@kbn/data-plugin/server'; +import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; +import { LogsSharedDomainLibs } from './lib/logs_shared_types'; +import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; + +export type LogsSharedPluginCoreSetup = CoreSetup< + LogsSharedServerPluginStartDeps, + LogsSharedPluginStart +>; +export type LogsSharedPluginStartServicesAccessor = LogsSharedPluginCoreSetup['getStartServices']; + +export interface LogsSharedPluginSetup extends LogsSharedDomainLibs { + logViews: LogViewsServiceSetup; + registerUsageCollectorActions: (usageCollector: UsageCollector) => void; +} + +export interface LogsSharedPluginStart { + logViews: LogViewsServiceStart; +} + +export interface LogsSharedServerPluginSetupDeps { + data: DataPluginSetup; +} + +export interface LogsSharedServerPluginStartDeps { + data: DataPluginStart; + dataViews: DataViewsPluginStart; +} + +export interface UsageCollector { + countLogs?: () => void; +} + +/** + * @internal + */ +export type LogsSharedPluginRequestHandlerContext = RequestHandlerContext; diff --git a/x-pack/plugins/logs_shared/server/utils/elasticsearch_runtime_types.ts b/x-pack/plugins/logs_shared/server/utils/elasticsearch_runtime_types.ts new file mode 100644 index 0000000000000..e2dbf02ae2d06 --- /dev/null +++ b/x-pack/plugins/logs_shared/server/utils/elasticsearch_runtime_types.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; + +export const shardFailureRT = rt.partial({ + index: rt.union([rt.string, rt.null]), + node: rt.union([rt.string, rt.null]), + reason: rt.partial({ + reason: rt.union([rt.string, rt.null]), + type: rt.union([rt.string, rt.null]), + }), + shard: rt.number, +}); + +export type ShardFailure = rt.TypeOf; + +export const commonSearchSuccessResponseFieldsRT = rt.type({ + _shards: rt.intersection([ + rt.type({ + total: rt.number, + successful: rt.number, + skipped: rt.number, + failed: rt.number, + }), + rt.partial({ + failures: rt.array(shardFailureRT), + }), + ]), + timed_out: rt.boolean, + took: rt.number, +}); + +export const commonHitFieldsRT = rt.type({ + _index: rt.string, + _id: rt.string, +}); diff --git a/x-pack/plugins/logs_shared/server/utils/serialized_query.ts b/x-pack/plugins/logs_shared/server/utils/serialized_query.ts new file mode 100644 index 0000000000000..b3b2569528aea --- /dev/null +++ b/x-pack/plugins/logs_shared/server/utils/serialized_query.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 { JsonObject } from '@kbn/utility-types'; + +export const parseFilterQuery = ( + filterQuery: string | null | undefined +): JsonObject | undefined => { + try { + if (filterQuery) { + const parsedFilterQuery = JSON.parse(filterQuery); + if ( + !parsedFilterQuery || + ['string', 'number', 'boolean'].includes(typeof parsedFilterQuery) || + Array.isArray(parsedFilterQuery) + ) { + throw new Error('expected value to be an object'); + } + return parsedFilterQuery; + } else { + return undefined; + } + } catch (err) { + throw new Error(`Failed to parse query: ${err}`); + } +}; diff --git a/x-pack/plugins/infra/server/utils/typed_search_strategy.ts b/x-pack/plugins/logs_shared/server/utils/typed_search_strategy.ts similarity index 100% rename from x-pack/plugins/infra/server/utils/typed_search_strategy.ts rename to x-pack/plugins/logs_shared/server/utils/typed_search_strategy.ts diff --git a/x-pack/plugins/logs_shared/tsconfig.json b/x-pack/plugins/logs_shared/tsconfig.json new file mode 100644 index 0000000000000..31c4466615974 --- /dev/null +++ b/x-pack/plugins/logs_shared/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": ["../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*", "types/**/*"], + "exclude": ["target/**/*"], + "kbn_references": [ + "@kbn/core", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/data-views-plugin", + "@kbn/io-ts-utils", + "@kbn/data-plugin", + "@kbn/kibana-utils-plugin", + "@kbn/es-query", + "@kbn/utility-types", + "@kbn/core-http-server", + "@kbn/logging", + "@kbn/config-schema", + "@kbn/std", + "@kbn/logging-mocks", + "@kbn/kibana-react-plugin", + "@kbn/test-subj-selector", + "@kbn/observability-shared-plugin", + "@kbn/observability-plugin", + "@kbn/datemath", + "@kbn/core-http-browser", + "@kbn/ui-actions-plugin", + ] +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx index e058af6847763..fa1ae148722eb 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx @@ -42,7 +42,10 @@ export const SingleMetricSettings: FC = ({ setIsValid }) => { - + = ({ setCurrentStep, isCurrentStep }) => {isAdvanced === false && ( - + = ({ existingJobsAndGroups, jobType }) => { return ( - :{' '} - {jobCreatorTitle} +
+ :{' '} + {jobCreatorTitle} +
diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 68b3ed3491503..31794a901416f 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -23,6 +23,7 @@ import { type MlAnomalyResultType, ML_ANOMALY_RESULT_TYPE, } from '@kbn/ml-anomaly-utils'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { MlClient } from '../ml_client'; import { MlAnomalyDetectionAlertParams, @@ -78,6 +79,7 @@ export function buildExplorerUrl( jobIds: string[], timeRange: { from: string; to: string; mode?: string }, type: MlAnomalyResultType, + spaceId: string, r?: AlertExecutionResult ): string { const isInfluencerResult = type === ML_ANOMALY_RESULT_TYPE.INFLUENCER; @@ -145,7 +147,11 @@ export function buildExplorerUrl( }, }, }; - return `/app/ml/explorer/?_g=${encodeURIComponent( + + const spacePathComponent: string = + !spaceId || spaceId === DEFAULT_SPACE_ID ? '' : `/s/${spaceId}`; + + return `${spacePathComponent}/app/ml/explorer/?_g=${encodeURIComponent( rison.encode(globalState) )}&_a=${encodeURIComponent(rison.encode(appState))}`; } @@ -765,9 +771,11 @@ export function alertingServiceProvider( * Return the result of an alert condition execution. * * @param params - Alert params + * @param spaceId */ execute: async ( - params: MlAnomalyDetectionAlertParams + params: MlAnomalyDetectionAlertParams, + spaceId: string ): Promise< { context: AnomalyDetectionAlertContext; name: string; isHealthy: boolean } | undefined > => { @@ -784,6 +792,7 @@ export function alertingServiceProvider( result.jobIds, { from: result.bucketRange.start, to: result.bucketRange.end }, params.resultType, + spaceId, result ); @@ -806,7 +815,8 @@ export function alertingServiceProvider( to: 'now', mode: 'relative', }, - queryParams.resultType + queryParams.resultType, + spaceId ), jobIds: queryParams.jobIds, message: i18n.translate( diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 7cee5917f6567..d68e40f44b4a6 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -137,13 +137,13 @@ export function registerAnomalyDetectionAlertType({ minimumLicenseRequired: MINIMUM_FULL_LICENSE, isExportable: true, doesSetRecoveryContext: true, - async executor({ services, params }) { + async executor({ services, params, spaceId }) { const fakeRequest = {} as KibanaRequest; const { execute } = mlSharedServices.alertingServiceProvider( services.savedObjectsClient, fakeRequest ); - const executionResult = await execute(params); + const executionResult = await execute(params, spaceId); if (executionResult && !executionResult.isHealthy) { const alertInstanceName = executionResult.name; diff --git a/x-pack/plugins/monitoring/kibana.jsonc b/x-pack/plugins/monitoring/kibana.jsonc index 236e6390d1bce..8da632c4b7d6f 100644 --- a/x-pack/plugins/monitoring/kibana.jsonc +++ b/x-pack/plugins/monitoring/kibana.jsonc @@ -19,6 +19,7 @@ ], "optionalPlugins": [ "infra", + "logsShared", "usageCollection", "home", "cloud", diff --git a/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts b/x-pack/plugins/monitoring/server/lib/logs/init_log_view.ts similarity index 74% rename from x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts rename to x-pack/plugins/monitoring/server/lib/logs/init_log_view.ts index 43fb8f7cc5dbd..52b0f43647386 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts +++ b/x-pack/plugins/monitoring/server/lib/logs/init_log_view.ts @@ -5,20 +5,20 @@ * 2.0. */ -import { InfraPluginSetup } from '@kbn/infra-plugin/server'; +import { LogsSharedPluginSetup } from '@kbn/logs-shared-plugin/server'; import { CCS_REMOTE_PATTERN, INFRA_SOURCE_ID } from '../../../common/constants'; import { MonitoringConfig } from '../../config'; import { getIndexPatterns } from '../cluster/get_index_patterns'; -export const initInfraSource = (config: MonitoringConfig, infraPlugin: InfraPluginSetup) => { - if (infraPlugin) { +export const initLogView = (config: MonitoringConfig, logsShared: LogsSharedPluginSetup) => { + if (logsShared) { const logsIndexPattern = getIndexPatterns({ config, type: 'logs', ccs: CCS_REMOTE_PATTERN, }); - infraPlugin.logViews.defineInternalLogView(INFRA_SOURCE_ID, { + logsShared.logViews.defineInternalLogView(INFRA_SOURCE_ID, { name: 'Elastic Stack Logs', logIndices: { type: 'index_name', diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.test.ts b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.test.ts index b743e0300aef6..e7ffe701fbd1a 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.test.ts +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.test.ts @@ -7,6 +7,7 @@ import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { infraPluginMock } from '@kbn/infra-plugin/server/mocks'; +import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { configSchema, createConfig } from '../../../config'; @@ -38,6 +39,7 @@ const mockReq = ( usageCollection: usageCollectionSetup, features: featuresPluginMock.createSetup(), infra: infraPluginMock.createSetupContract(), + logsShared: logsSharedPluginMock.createSetupContract(), }, }, }, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 5996eb125b6dc..1747855112964 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -35,7 +35,7 @@ import { configSchema, createConfig, MonitoringConfig } from './config'; import { instantiateClient } from './es_client/instantiate_client'; import { initBulkUploader } from './kibana_monitoring'; import { registerCollectors } from './kibana_monitoring/collectors'; -import { initInfraSource } from './lib/logs/init_infra_source'; +import { initLogView } from './lib/logs/init_log_view'; import { LicenseService } from './license_service'; import { requireUIRoutes } from './routes'; import { EndpointTypes, Globals } from './static_globals'; @@ -202,7 +202,7 @@ export class MonitoringPlugin alerting: plugins.alerting, logger: this.log, }); - initInfraSource(config, plugins.infra); + initLogView(config, plugins.logsShared); } } diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 64931f5888514..7e056cbac5fb8 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -35,6 +35,7 @@ import { PluginSetupContract as FeaturesPluginSetupContract } from '@kbn/feature import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { CloudSetup } from '@kbn/cloud-plugin/server'; import { RouteConfig, RouteMethod, Headers } from '@kbn/core/server'; +import { LogsSharedPluginSetup } from '@kbn/logs-shared-plugin/server'; import { ElasticsearchModifiedSource } from '../common/types/es'; import { RulesByType } from '../common/types/alerts'; import { configSchema, MonitoringConfig } from './config'; @@ -56,6 +57,7 @@ export interface PluginsSetup { alerting?: AlertingPluginSetupContract; infra: InfraPluginSetup; cloud?: CloudSetup; + logsShared: LogsSharedPluginSetup; } export type RequestHandlerContextMonitoringPlugin = CustomRequestHandlerContext<{ diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json index 5ebd037e04648..00ca962568141 100644 --- a/x-pack/plugins/monitoring/tsconfig.json +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -41,6 +41,7 @@ "@kbn/shared-ux-router", "@kbn/observability-shared-plugin", "@kbn/shared-ux-link-redirect-app", + "@kbn/logs-shared-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/observability/dev_docs/slo.md b/x-pack/plugins/observability/dev_docs/slo.md index 908bf0a031be4..ab605e51ffd27 100644 --- a/x-pack/plugins/observability/dev_docs/slo.md +++ b/x-pack/plugins/observability/dev_docs/slo.md @@ -337,17 +337,17 @@ curl --request POST \ "field": "processor.processed" } ], - equation: 'A' + "equation": "A" }, "total": { "metrics": [ { "name": "A", "aggregation": "sum", - "processor.accepted" + "field": "processor.accepted" } ], - equation: 'A' + "equation": "A" }, "filter": "", "timestampField": "@timestamp" diff --git a/x-pack/plugins/observability/docs/openapi/slo/README.md b/x-pack/plugins/observability/docs/openapi/slo/README.md index e128dd32a38b9..440e560bf62f1 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/README.md +++ b/x-pack/plugins/observability/docs/openapi/slo/README.md @@ -7,28 +7,28 @@ A guide about the OpenApi specification can be found at [https://swagger.io/docs ## The `openapi/slo` folder -* `entrypoint.yaml` is the overview file which pulls together all the paths and components. -* [Paths](paths/README.md): this defines each endpoint. A path can have one operation per http method. -* [Components](components/README.md): Reusable components +- `entrypoint.yaml` is the overview file which pulls together all the paths and components. +- [Paths](paths/README.md): this defines each endpoint. A path can have one operation per http method. +- [Components](components/README.md): Reusable components ## Tools It is possible to validate the docs before bundling them with the following command in the `x-pack/plugins/observability/docs/openapi/slo` folder: - ```bash - npx swagger-cli validate entrypoint.yaml - ``` +```bash + npx swagger-cli validate entrypoint.yaml +``` Then you can generate the `bundled` files by running the following commands: - ```bash - npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml - npx @redocly/cli bundle entrypoint.yaml --output bundled.json --ext json - ``` +```bash + npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml + npx @redocly/cli bundle entrypoint.yaml --output bundled.json --ext json +``` After generating the json bundle ensure that it is also valid by running the following command: - ```bash - npx @redocly/cli lint bundled.json - ``` +```bash + npx @redocly/cli lint bundled.json +``` diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.json b/x-pack/plugins/observability/docs/openapi/slo/bundled.json new file mode 100644 index 0000000000000..09295ed296678 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.json @@ -0,0 +1,2259 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "SLOs", + "description": "OpenAPI schema for SLOs endpoints", + "version": "1.0", + "contact": { + "name": "Actionable Observability Team" + }, + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "servers": [ + { + "url": "http://localhost:5601", + "description": "local" + } + ], + "security": [ + { + "basicAuth": [] + }, + { + "apiKeyAuth": [] + } + ], + "tags": [ + { + "name": "slo", + "description": "SLO APIs enable you to define, manage and track service-level objectives" + }, + { + "name": "composite slo", + "description": "Composite SLO APIs enable you to define, manage and track a group of SLOs." + } + ], + "paths": { + "/s/{spaceId}/api/observability/composite_slos": { + "post": { + "summary": "Creates a Composite SLO", + "operationId": "createCompositeSlo", + "description": "You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "composite slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create_composite_slo_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create_composite_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "409": { + "description": "Conflict - The Composite SLO id already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/409_response" + } + } + } + } + } + }, + "get": { + "summary": "Retrieves a paginated list of composite SLOs with summary", + "operationId": "findCompositeSlo", + "description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "composite slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "name": "page", + "in": "query", + "description": "The page number to return", + "schema": { + "type": "integer", + "default": 1 + }, + "example": 1 + }, + { + "name": "perPage", + "in": "query", + "description": "The number of SLOs to return per page", + "schema": { + "type": "integer", + "default": 25 + }, + "example": 20 + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort by field", + "schema": { + "type": "string", + "enum": [ + "creationTime" + ], + "default": "creationTime" + }, + "example": "creationTime" + }, + { + "name": "sortDirection", + "in": "query", + "description": "Sort order", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + }, + "example": "asc" + } + ], + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/find_composite_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/observability/composite_slos/{compositeSloId}": { + "get": { + "summary": "Retrieves a composite SLO", + "operationId": "getCompositeSlo", + "description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "composite slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/composite_slo_id" + } + ], + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/composite_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "put": { + "summary": "Updates a composite SLO", + "operationId": "updateCompositeSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "composite slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/composite_slo_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/update_composite_slo_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/base_composite_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "delete": { + "summary": "Deletes a composite SLO", + "operationId": "deleteCompositeSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "composite slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/composite_slo_id" + } + ], + "responses": { + "204": { + "description": "Successful request" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/observability/slos": { + "post": { + "summary": "Creates an SLO.", + "operationId": "createSlo", + "description": "You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create_slo_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "409": { + "description": "Conflict - The SLO id already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/409_response" + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "get": { + "summary": "Retrieves a paginated list of SLOs", + "operationId": "findSlos", + "description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "name": "name", + "in": "query", + "description": "Filter by name", + "schema": { + "type": "string" + }, + "example": "awesome-service" + }, + { + "name": "indicatorTypes", + "in": "query", + "description": "Filter by indicator type", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "sli.kql.custom" + ] + }, + { + "name": "page", + "in": "query", + "description": "The page number to return", + "schema": { + "type": "integer", + "default": 1 + }, + "example": 1 + }, + { + "name": "perPage", + "in": "query", + "description": "The number of SLOs to return per page", + "schema": { + "type": "integer", + "default": 25 + }, + "example": 20 + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort by field", + "schema": { + "type": "string", + "enum": [ + "creationTime", + "indicatorType" + ], + "default": "creationTime" + }, + "example": "creationTime" + }, + { + "name": "sortDirection", + "in": "query", + "description": "Sort order", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + }, + "example": "asc" + } + ], + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/find_slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/observability/slos/{sloId}": { + "get": { + "summary": "Retrieves a SLO", + "operationId": "getSlo", + "description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/slo_id" + } + ], + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "put": { + "summary": "Updates an SLO", + "operationId": "updateSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/slo_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/update_slo_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/slo_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "delete": { + "summary": "Deletes an SLO", + "operationId": "deleteSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/slo_id" + } + ], + "responses": { + "204": { + "description": "Successful request" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/observability/slos/{sloId}/enable": { + "post": { + "summary": "Enables an SLO", + "operationId": "enableSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/slo_id" + } + ], + "responses": { + "204": { + "description": "Successful request" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/observability/slos/{sloId}/disable": { + "post": { + "summary": "Disables an SLO", + "operationId": "disableSlo", + "description": "You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/slo_id" + } + ], + "responses": { + "200": { + "description": "Successful request" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + }, + "404": { + "description": "Not found response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/internal/observability/slos/_historical_summary": { + "post": { + "summary": "Retrieves the historical summary for a list of SLOs", + "operationId": "historicalSummary", + "description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/historical_summary_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/historical_summary_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "ApiKey" + } + }, + "parameters": { + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "description": "Cross-site request forgery protection", + "required": true + }, + "space_id": { + "in": "path", + "name": "spaceId", + "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", + "required": true, + "schema": { + "type": "string", + "example": "default" + } + }, + "composite_slo_id": { + "in": "path", + "name": "compositeSloId", + "description": "An identifier for the composite slo.", + "required": true, + "schema": { + "type": "string", + "example": "9c235211-6834-11ea-a78c-6feb38a34414" + } + }, + "slo_id": { + "in": "path", + "name": "sloId", + "description": "An identifier for the slo.", + "required": true, + "schema": { + "type": "string", + "example": "9c235211-6834-11ea-a78c-6feb38a34414" + } + } + }, + "schemas": { + "time_window_rolling": { + "title": "Rolling", + "required": [ + "duration", + "isRolling" + ], + "description": "Defines properties for rolling time window", + "type": "object", + "properties": { + "duration": { + "description": "the duration formatted as {duration}{unit}", + "type": "string", + "example": "28d" + }, + "isRolling": { + "description": "Indicates a rolling time window", + "type": "boolean", + "example": true + } + } + }, + "budgeting_method": { + "title": "Budgeting method", + "type": "string", + "description": "The budgeting method to use when computing the rollup data.", + "enum": [ + "occurrences", + "timeslices" + ], + "example": "occurrences" + }, + "composite_method": { + "title": "Composite method", + "type": "string", + "description": "The composite method to use for the composite SLO.", + "enum": [ + "weightedAverage" + ], + "example": "weightedAverage" + }, + "objective": { + "title": "Objective", + "required": [ + "target" + ], + "description": "Defines properties for the SLO objective", + "type": "object", + "properties": { + "target": { + "description": "the target objective between 0 and 1 excluded", + "type": "number", + "example": 0.99 + }, + "timeslicesTarget": { + "description": "the target objective for each slice when using a timeslices budgeting method", + "type": "number", + "example": 0.995 + }, + "timeslicesWindow": { + "description": "the duration of each slice when using a timeslices budgeting method, as {duraton}{unit}", + "type": "string", + "example": "5m" + } + } + }, + "weighted_composite_sources": { + "title": "Weighted sources", + "description": "An array of source SLO to use for the weighted average composite.", + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "revision", + "weight" + ], + "properties": { + "id": { + "description": "The id of the SLO.", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + }, + "revision": { + "description": "The revision number of the SLO.", + "type": "number", + "example": 2 + }, + "weight": { + "description": "The weight to apply to this SLO.", + "type": "number", + "example": 3 + } + } + } + }, + "error_budget": { + "title": "Error budget", + "type": "object", + "properties": { + "initial": { + "type": "number", + "description": "The initial error budget, as 1 - objective", + "example": 0.02 + }, + "consumed": { + "type": "number", + "description": "The error budget consummed, as a percentage of the initial value.", + "example": 0.8 + }, + "remaining": { + "type": "number", + "description": "The error budget remaining, as a percentage of the initial value.", + "example": 0.2 + }, + "isEstimated": { + "type": "boolean", + "description": "Only for SLO defined with occurrences budgeting method and calendar aligned time window.", + "example": true + } + } + }, + "summary": { + "title": "Summary", + "type": "object", + "description": "The SLO computed data", + "properties": { + "status": { + "type": "string", + "enum": [ + "NO_DATA", + "HEALTHY", + "DEGRADING", + "VIOLATED" + ], + "example": "HEALTHY" + }, + "sliValue": { + "type": "number", + "example": 0.9836 + }, + "errorBudget": { + "$ref": "#/components/schemas/error_budget" + } + } + }, + "composite_slo_response": { + "title": "Composite SLO with summary response", + "type": "object", + "properties": { + "id": { + "description": "The identifier of the composite SLO.", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + }, + "name": { + "description": "The name of the composite SLO.", + "type": "string", + "example": "My Service SLO" + }, + "timeWindow": { + "$ref": "#/components/schemas/time_window_rolling" + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "compositeMethod": { + "$ref": "#/components/schemas/composite_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "sources": { + "oneOf": [ + { + "$ref": "#/components/schemas/weighted_composite_sources" + } + ] + }, + "summary": { + "$ref": "#/components/schemas/summary" + }, + "createdAt": { + "description": "The creation date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + }, + "updatedAt": { + "description": "The last update date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + } + } + }, + "find_composite_slo_response": { + "title": "Find composite SLO response", + "description": "A paginated response of composite SLOs matching the query.", + "type": "object", + "properties": { + "page": { + "type": "number", + "example": 1 + }, + "perPage": { + "type": "number", + "example": 25 + }, + "total": { + "type": "number", + "example": 34 + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/composite_slo_response" + } + } + } + }, + "400_response": { + "title": "Bad request", + "type": "object", + "required": [ + "statusCode", + "error", + "message" + ], + "properties": { + "statusCode": { + "type": "number", + "example": 400 + }, + "error": { + "type": "string", + "example": "Bad Request" + }, + "message": { + "type": "string", + "example": "Invalid value 'foo' supplied to: [...]" + } + } + }, + "401_response": { + "title": "Unauthorized", + "type": "object", + "required": [ + "statusCode", + "error", + "message" + ], + "properties": { + "statusCode": { + "type": "number", + "example": 401 + }, + "error": { + "type": "string", + "example": "Unauthorized" + }, + "message": { + "type": "string", + "example": "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" + } + } + }, + "403_response": { + "title": "Unauthorized", + "type": "object", + "required": [ + "statusCode", + "error", + "message" + ], + "properties": { + "statusCode": { + "type": "number", + "example": 403 + }, + "error": { + "type": "string", + "example": "Unauthorized" + }, + "message": { + "type": "string", + "example": "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" + } + } + }, + "404_response": { + "title": "Not found", + "type": "object", + "required": [ + "statusCode", + "error", + "message" + ], + "properties": { + "statusCode": { + "type": "number", + "example": 404 + }, + "error": { + "type": "string", + "example": "Not Found" + }, + "message": { + "type": "string", + "example": "SLO [3749f390-03a3-11ee-8139-c7ff60a1692d] not found" + } + } + }, + "create_composite_slo_request": { + "title": "Create composite SLO request", + "description": "The create Composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO.\n", + "type": "object", + "required": [ + "name", + "timeWindow", + "budgetingMethod", + "compositeMethod", + "objective", + "sources" + ], + "properties": { + "id": { + "description": "A unique identifier for the composite SLO. Must be between 8 and 36 chars", + "type": "string", + "example": "my-super-composite-slo-id" + }, + "name": { + "description": "A name for the composite SLO.", + "type": "string" + }, + "timeWindow": { + "$ref": "#/components/schemas/time_window_rolling" + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "compositeMethod": { + "$ref": "#/components/schemas/composite_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "sources": { + "oneOf": [ + { + "$ref": "#/components/schemas/weighted_composite_sources" + } + ] + } + } + }, + "create_composite_slo_response": { + "title": "Create composite SLO response", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + } + }, + "409_response": { + "title": "Conflict", + "type": "object", + "required": [ + "statusCode", + "error", + "message" + ], + "properties": { + "statusCode": { + "type": "number", + "example": 409 + }, + "error": { + "type": "string", + "example": "Conflict" + }, + "message": { + "type": "string", + "example": "SLO [d077e940-1515-11ee-9c50-9d096392f520] already exists" + } + } + }, + "update_composite_slo_request": { + "title": "Update composite SLO request", + "description": "The update composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO.\n", + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for the composite SLO. Must be between 8 and 36 chars", + "type": "string", + "example": "my-super-composite-slo-id" + }, + "name": { + "description": "A name for the composite SLO.", + "type": "string" + }, + "timeWindow": { + "$ref": "#/components/schemas/time_window_rolling" + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "compositeMethod": { + "$ref": "#/components/schemas/composite_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "sources": { + "oneOf": [ + { + "$ref": "#/components/schemas/weighted_composite_sources" + } + ] + } + } + }, + "base_composite_slo_response": { + "title": "Composite SLO response", + "type": "object", + "properties": { + "id": { + "description": "The identifier of the composite SLO.", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + }, + "name": { + "description": "The name of the composite SLO.", + "type": "string", + "example": "My Service SLO" + }, + "timeWindow": { + "$ref": "#/components/schemas/time_window_rolling" + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "compositeMethod": { + "$ref": "#/components/schemas/composite_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "sources": { + "oneOf": [ + { + "$ref": "#/components/schemas/weighted_composite_sources" + } + ] + }, + "createdAt": { + "description": "The creation date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + }, + "updatedAt": { + "description": "The last update date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + } + } + }, + "indicator_properties_custom_kql": { + "title": "Custom KQL", + "required": [ + "type", + "params" + ], + "description": "Defines properties for a custom KQL indicator type", + "type": "object", + "properties": { + "params": { + "description": "An object containing the indicator parameters.", + "type": "object", + "nullable": false, + "required": [ + "index", + "timestampField" + ], + "properties": { + "index": { + "description": "The index or index pattern to use", + "type": "string", + "example": "my-service-*" + }, + "filter": { + "description": "the KQL query to filter the documents with.", + "type": "string", + "example": "field.environment : \"production\" and service.name : \"my-service\"" + }, + "good": { + "description": "the KQL query used to define the good events.", + "type": "string", + "example": "request.latency <= 150 and request.status_code : \"2xx\"" + }, + "total": { + "description": "the KQL query used to define all events.", + "type": "string", + "example": "" + }, + "timestampField": { + "description": "The timestamp field used in the source indice.\n", + "type": "string", + "example": "timestamp" + } + } + }, + "type": { + "description": "The type of indicator.", + "type": "string", + "example": "sli.kql.custom" + } + } + }, + "indicator_properties_apm_availability": { + "title": "APM availability", + "required": [ + "type", + "params" + ], + "description": "Defines properties for the APM availability indicator type", + "type": "object", + "properties": { + "params": { + "description": "An object containing the indicator parameters.", + "type": "object", + "nullable": false, + "required": [ + "service", + "environment", + "transactionType", + "transactionName", + "index" + ], + "properties": { + "service": { + "description": "The APM service name", + "type": "string", + "example": "o11y-app" + }, + "environment": { + "description": "The APM service environment or \"*\"", + "type": "string", + "example": "production" + }, + "transactionType": { + "description": "The APM transaction type or \"*\"", + "type": "string", + "example": "request" + }, + "transactionName": { + "description": "The APM transaction name or \"*\"", + "type": "string", + "example": "GET /my/api" + }, + "filter": { + "description": "KQL query used for filtering the data", + "type": "string", + "example": "service.foo : \"bar\"" + }, + "index": { + "description": "The index used by APM metrics", + "type": "string", + "example": "metrics-apm*,apm*" + } + } + }, + "type": { + "description": "The type of indicator.", + "type": "string", + "example": "sli.apm.transactionDuration" + } + } + }, + "indicator_properties_apm_latency": { + "title": "APM latency", + "required": [ + "type", + "params" + ], + "description": "Defines properties for the APM latency indicator type", + "type": "object", + "properties": { + "params": { + "description": "An object containing the indicator parameters.", + "type": "object", + "nullable": false, + "required": [ + "service", + "environment", + "transactionType", + "transactionName", + "index", + "threshold" + ], + "properties": { + "service": { + "description": "The APM service name", + "type": "string", + "example": "o11y-app" + }, + "environment": { + "description": "The APM service environment or \"*\"", + "type": "string", + "example": "production" + }, + "transactionType": { + "description": "The APM transaction type or \"*\"", + "type": "string", + "example": "request" + }, + "transactionName": { + "description": "The APM transaction name or \"*\"", + "type": "string", + "example": "GET /my/api" + }, + "filter": { + "description": "KQL query used for filtering the data", + "type": "string", + "example": "service.foo : \"bar\"" + }, + "index": { + "description": "The index used by APM metrics", + "type": "string", + "example": "metrics-apm*,apm*" + }, + "threshold": { + "description": "The latency threshold in milliseconds", + "type": "number", + "example": 250 + } + } + }, + "type": { + "description": "The type of indicator.", + "type": "string", + "example": "sli.apm.transactionDuration" + } + } + }, + "indicator_properties_custom_metric": { + "title": "Custom metric", + "required": [ + "type", + "params" + ], + "description": "Defines properties for a custom metric indicator type", + "type": "object", + "properties": { + "params": { + "description": "An object containing the indicator parameters.", + "type": "object", + "nullable": false, + "required": [ + "index", + "timestampField", + "good", + "total" + ], + "properties": { + "index": { + "description": "The index or index pattern to use", + "type": "string", + "example": "my-service-*" + }, + "filter": { + "description": "the KQL query to filter the documents with.", + "type": "string", + "example": "field.environment : \"production\" and service.name : \"my-service\"" + }, + "timestampField": { + "description": "The timestamp field used in the source indice.\n", + "type": "string", + "example": "timestamp" + }, + "good": { + "description": "An object defining the \"good\" metrics and equation\n", + "type": "object", + "required": [ + "metrics", + "equation" + ], + "properties": { + "metrics": { + "description": "List of metrics with their name, aggregation type, and field.", + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "aggregation", + "field" + ], + "properties": { + "name": { + "description": "The name of the metric. Only valid options are A-Z", + "type": "string", + "example": "A", + "pattern": "^[A-Z]$" + }, + "aggregation": { + "description": "The aggregation type of the metric. Only valid option is \"sum\"", + "type": "string", + "example": "sum", + "enum": [ + "sum" + ] + }, + "field": { + "description": "The field of the metric.", + "type": "string", + "example": "processor.processed" + } + } + } + }, + "equation": { + "description": "The equation to calculate the \"good\" metric.", + "type": "string", + "example": "A" + } + } + }, + "total": { + "description": "An object defining the \"total\" metrics and equation\n", + "type": "object", + "required": [ + "metrics", + "equation" + ], + "properties": { + "metrics": { + "description": "List of metrics with their name, aggregation type, and field.", + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "aggregation", + "field" + ], + "properties": { + "name": { + "description": "The name of the metric. Only valid options are A-Z", + "type": "string", + "example": "A", + "pattern": "^[A-Z]$" + }, + "aggregation": { + "description": "The aggregation type of the metric. Only valid option is \"sum\"", + "type": "string", + "example": "sum", + "enum": [ + "sum" + ] + }, + "field": { + "description": "The field of the metric.", + "type": "string", + "example": "processor.processed" + } + } + } + }, + "equation": { + "description": "The equation to calculate the \"total\" metric.", + "type": "string", + "example": "A" + } + } + } + } + }, + "type": { + "description": "The type of indicator.", + "type": "string", + "example": "sli.metric.custom" + } + } + }, + "time_window_calendar_aligned": { + "title": "Calendar aligned", + "required": [ + "duration", + "isCalendar" + ], + "description": "Defines properties for calendar aligned time window", + "type": "object", + "properties": { + "duration": { + "description": "the duration formatted as {duration}{unit}, accept '1w' (weekly calendar) or '1M' (monthly calendar) only", + "type": "string", + "example": "1M" + }, + "isCalendar": { + "description": "Indicates a calendar aligned time window", + "type": "boolean", + "example": true + } + } + }, + "settings": { + "title": "Settings", + "description": "Defines properties for SLO settings.", + "type": "object", + "properties": { + "syncDelay": { + "description": "The synch delay to apply to the transform. Default 1m", + "type": "string", + "example": "5m" + }, + "frequency": { + "description": "Configure how often the transform runs, default 1m", + "type": "string", + "example": "5m" + } + } + }, + "slo_response": { + "title": "SLO response", + "type": "object", + "properties": { + "id": { + "description": "The identifier of the SLO.", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + }, + "name": { + "description": "The name of the SLO.", + "type": "string", + "example": "My Service SLO" + }, + "description": { + "description": "The description of the SLO.", + "type": "string", + "example": "My SLO description" + }, + "indicator": { + "oneOf": [ + { + "$ref": "#/components/schemas/indicator_properties_custom_kql" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_availability" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_latency" + }, + { + "$ref": "#/components/schemas/indicator_properties_custom_metric" + } + ] + }, + "timeWindow": { + "oneOf": [ + { + "$ref": "#/components/schemas/time_window_rolling" + }, + { + "$ref": "#/components/schemas/time_window_calendar_aligned" + } + ] + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "settings": { + "$ref": "#/components/schemas/settings" + }, + "revision": { + "description": "The SLO revision", + "type": "number", + "example": 2 + }, + "summary": { + "$ref": "#/components/schemas/summary" + }, + "enabled": { + "description": "Indicate if the SLO is enabled", + "type": "boolean", + "example": true + }, + "createdAt": { + "description": "The creation date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + }, + "updatedAt": { + "description": "The last update date", + "type": "string", + "example": "2023-01-12T10:03:19.000Z" + } + } + }, + "find_slo_response": { + "title": "Find SLO response", + "description": "A paginated response of SLOs matching the query.\n", + "type": "object", + "properties": { + "page": { + "type": "number", + "example": 1 + }, + "perPage": { + "type": "number", + "example": 25 + }, + "total": { + "type": "number", + "example": 34 + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/slo_response" + } + } + } + }, + "create_slo_request": { + "title": "Create SLO request", + "description": "The create SLO API request body varies depending on the type of indicator, time window and budgeting method.\n", + "type": "object", + "required": [ + "name", + "description", + "indicator", + "timeWindow", + "budgetingMethod", + "objective" + ], + "properties": { + "id": { + "description": "A optional and unique identifier for the SLO. Must be between 8 and 36 chars", + "type": "string", + "example": "my-super-slo-id" + }, + "name": { + "description": "A name for the SLO.", + "type": "string" + }, + "description": { + "description": "A description for the SLO.", + "type": "string" + }, + "indicator": { + "oneOf": [ + { + "$ref": "#/components/schemas/indicator_properties_custom_kql" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_availability" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_latency" + }, + { + "$ref": "#/components/schemas/indicator_properties_custom_metric" + } + ] + }, + "timeWindow": { + "oneOf": [ + { + "$ref": "#/components/schemas/time_window_rolling" + }, + { + "$ref": "#/components/schemas/time_window_calendar_aligned" + } + ] + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "settings": { + "$ref": "#/components/schemas/settings" + } + } + }, + "create_slo_response": { + "title": "Create SLO response", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + } + }, + "update_slo_request": { + "title": "Update SLO request", + "description": "The update SLO API request body varies depending on the type of indicator, time window and budgeting method. Partial update is handled.\n", + "type": "object", + "properties": { + "name": { + "description": "A name for the SLO.", + "type": "string" + }, + "description": { + "description": "A description for the SLO.", + "type": "string" + }, + "indicator": { + "oneOf": [ + { + "$ref": "#/components/schemas/indicator_properties_custom_kql" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_availability" + }, + { + "$ref": "#/components/schemas/indicator_properties_apm_latency" + }, + { + "$ref": "#/components/schemas/indicator_properties_custom_metric" + } + ] + }, + "timeWindow": { + "oneOf": [ + { + "$ref": "#/components/schemas/time_window_rolling" + }, + { + "$ref": "#/components/schemas/time_window_calendar_aligned" + } + ] + }, + "budgetingMethod": { + "$ref": "#/components/schemas/budgeting_method" + }, + "objective": { + "$ref": "#/components/schemas/objective" + }, + "settings": { + "$ref": "#/components/schemas/settings" + } + } + }, + "historical_summary_request": { + "title": "Historical summary request", + "type": "object", + "required": [ + "sloIds" + ], + "properties": { + "sloIds": { + "description": "The list of SLO identifiers to get the historical summary for", + "type": "array", + "items": { + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + } + } + }, + "historical_summary_response": { + "title": "Historical summary response", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "example": "2022-01-01T00:00:00.000Z" + }, + "status": { + "type": "string", + "enum": [ + "NO_DATA", + "HEALTHY", + "DEGRADING", + "VIOLATED" + ], + "example": "HEALTHY" + }, + "sliValue": { + "type": "number", + "example": 0.9836 + }, + "errorBudget": { + "$ref": "#/components/schemas/error_budget" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml index 59d600a9e1375..f4ef3a5991387 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml @@ -8,14 +8,17 @@ info: license: name: Elastic License 2.0 url: https://www.elastic.co/licensing/elastic-license +servers: + - url: http://localhost:5601 + description: local +security: + - basicAuth: [] + - apiKeyAuth: [] tags: - name: slo description: SLO APIs enable you to define, manage and track service-level objectives - name: composite slo description: Composite SLO APIs enable you to define, manage and track a group of SLOs. -servers: - - url: http://localhost:5601 - description: local paths: /s/{spaceId}/api/observability/composite_slos: post: @@ -53,6 +56,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '409': description: Conflict - The Composite SLO id already exists content: @@ -121,6 +130,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -158,6 +173,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -200,6 +221,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -232,6 +259,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -274,6 +307,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '409': description: Conflict - The SLO id already exists content: @@ -360,6 +399,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -397,6 +442,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -439,6 +490,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -471,6 +528,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -504,6 +567,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -537,6 +606,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' '404': description: Not found response content: @@ -579,6 +654,12 @@ paths: application/json: schema: $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' components: securitySchemes: basicAuth: @@ -817,6 +898,23 @@ components: message: type: string example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" + 403_response: + title: Unauthorized + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 403 + error: + type: string + example: Unauthorized + message: + type: string + example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" 404_response: title: Not found type: object @@ -1405,6 +1503,3 @@ components: example: 0.9836 errorBudget: $ref: '#/components/schemas/error_budget' -security: - - basicAuth: [] - - apiKeyAuth: [] diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/403_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/403_response.yaml new file mode 100644 index 0000000000000..24fcbad202a83 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/403_response.yaml @@ -0,0 +1,16 @@ +title: Unauthorized +type: object +required: + - statusCode + - error + - message +properties: + statusCode: + type: number + example: 403 + error: + type: string + example: Unauthorized + message: + type: string + example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml index e8e3cf75decc3..d77431452136a 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml @@ -24,16 +24,22 @@ post: $ref: '../components/schemas/create_composite_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '409': description: Conflict - The Composite SLO id already exists content: @@ -91,19 +97,25 @@ get: $ref: '../components/schemas/find_composite_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: - $ref: '../components/schemas/404_response.yaml' \ No newline at end of file + $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml index 54b3613c7a5c4..bca3976b80fcd 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml @@ -19,19 +19,25 @@ get: $ref: '../components/schemas/composite_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: $ref: '../components/schemas/404_response.yaml' @@ -63,19 +69,25 @@ put: $ref: '../components/schemas/base_composite_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: $ref: '../components/schemas/404_response.yaml' @@ -97,19 +109,25 @@ delete: description: Successful request '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml index 47fec9eee6494..68c0c602448dc 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml @@ -24,16 +24,22 @@ post: $ref: '../components/schemas/create_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '409': description: Conflict - The SLO id already exists content: @@ -65,9 +71,9 @@ get: description: Filter by indicator type schema: type: array - items: + items: type: string - example: ["sli.kql.custom"] + example: ['sli.kql.custom'] - name: page in: query description: The page number to return @@ -107,19 +113,25 @@ get: $ref: '../components/schemas/find_slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: - $ref: '../components/schemas/404_response.yaml' \ No newline at end of file + $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_historical_summary.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_historical_summary.yaml index 48ffe492a5a2b..6ce99505894cf 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_historical_summary.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_historical_summary.yaml @@ -18,19 +18,25 @@ post: responses: '200': description: Successful request - content: + content: application/json: schema: $ref: '../components/schemas/historical_summary_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}.yaml index cf31ccb2de8bf..66a5ddd7825a8 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}.yaml @@ -19,19 +19,25 @@ get: $ref: '../components/schemas/slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: $ref: '../components/schemas/404_response.yaml' @@ -63,19 +69,25 @@ put: $ref: '../components/schemas/slo_response.yaml' '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: $ref: '../components/schemas/404_response.yaml' @@ -97,19 +109,25 @@ delete: description: Successful request '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: - $ref: '../components/schemas/404_response.yaml' \ No newline at end of file + $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml index ace5b805cf92f..4932e9cf78c36 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml @@ -15,19 +15,25 @@ post: description: Successful request '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: - $ref: '../components/schemas/404_response.yaml' \ No newline at end of file + $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml index d751535fe365a..4ddda2bc94b60 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml @@ -15,19 +15,25 @@ post: description: Successful request '400': description: Bad request - content: + content: application/json: schema: $ref: '../components/schemas/400_response.yaml' '401': description: Unauthorized response - content: + content: application/json: schema: $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' '404': description: Not found response - content: + content: application/json: schema: - $ref: '../components/schemas/404_response.yaml' \ No newline at end of file + $ref: '../components/schemas/404_response.yaml' diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index 2d71fca18eede..6e73398527523 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -76,7 +76,7 @@ export function HeaderControl({ isLoading, slo }: Props) { params: { sloId: slo.id }, }, { - replace: true, + replace: false, } ); } 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 3d9e70f68dc6e..374b64b567404 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 @@ -106,7 +106,7 @@ export function SloListItem({ params: { sloId: slo.id }, }, { - replace: true, + replace: false, } ); }; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index e6e5412cc046e..8e28412050920 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -70,7 +70,7 @@ export const registerObservabilityRuleTypes = ( }, iconClass: 'bell', documentationUrl(docLinks) { - return 'https://www.elastic.co/guide/en/observability/current/slo-burn-rate-alert.html'; + return `${docLinks.links.observability.sloBurnRateRule}`; }, ruleParamsExpression: lazy(() => import('../components/burn_rate_rule_editor')), validate: validateBurnRateRule, @@ -78,6 +78,7 @@ export const registerObservabilityRuleTypes = ( defaultActionMessage: sloBurnRateDefaultActionMessage, defaultRecoveryMessage: sloBurnRateDefaultRecoveryMessage, }); + if (config.unsafe.thresholdRule.enabled) { observabilityRuleTypeRegistry.register({ id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 52c6aa3cff0d4..f41d37d4fc573 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { badRequest, forbidden, failedDependency } from '@hapi/boom'; +import { forbidden, failedDependency } from '@hapi/boom'; import { createSLOParamsSchema, deleteSLOParamsSchema, @@ -68,7 +68,7 @@ const createSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -95,7 +95,7 @@ const updateSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -127,7 +127,7 @@ const deleteSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -153,7 +153,7 @@ const getSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const soClient = (await context.core).savedObjects.client; @@ -178,7 +178,7 @@ const enableSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const soClient = (await context.core).savedObjects.client; @@ -204,7 +204,7 @@ const disableSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const soClient = (await context.core).savedObjects.client; @@ -230,7 +230,7 @@ const findSLORoute = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const soClient = (await context.core).savedObjects.client; @@ -255,7 +255,7 @@ const fetchHistoricalSummary = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const soClient = (await context.core).savedObjects.client; @@ -317,7 +317,7 @@ const getSloBurnRates = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -340,7 +340,7 @@ const getPreviewData = createObservabilityServerRoute({ const hasCorrectLicense = await isLicenseAtLeastPlatinum(context); if (!hasCorrectLicense) { - throw badRequest('Platinum license or higher is needed to make use of this feature.'); + throw forbidden('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; diff --git a/x-pack/plugins/profiling/public/views/no_data_view/index.tsx b/x-pack/plugins/profiling/public/views/no_data_view/index.tsx index a6bd9159d8c7d..9958089193931 100644 --- a/x-pack/plugins/profiling/public/views/no_data_view/index.tsx +++ b/x-pack/plugins/profiling/public/views/no_data_view/index.tsx @@ -329,7 +329,7 @@ docker.elastic.co/observability/profiling-agent:${hostAgentVersion} /root/pf-hos iconType="gear" fill href={`${core.http.basePath.prepend( - '/app/integrations/detail/profiler_agent-8.8.0-preview/overview?prerelease=true' + `/app/integrations/detail/profiler_agent-${data?.profilerAgent.version}/overview?prerelease=true` )}`} > {i18n.translate('xpack.profiling.tabs.elasticAgentIntegrarion.step2.button', { diff --git a/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts b/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts index df6da32a11b86..569c60a9e58a9 100644 --- a/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts +++ b/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts @@ -7,6 +7,7 @@ import { SavedObjectsClientContract } from '@kbn/core/server'; import { PackagePolicyClient } from '@kbn/fleet-plugin/server'; +import { fetchFindLatestPackageOrThrow } from '@kbn/fleet-plugin/server/services/epm/registry'; import { getCollectorPolicy } from './fleet_policies'; export interface SetupDataCollectionInstructions { @@ -17,6 +18,9 @@ export interface SetupDataCollectionInstructions { symbolizer: { host?: string; }; + profilerAgent: { + version: string; + }; } export async function getSetupInstructions({ @@ -28,6 +32,7 @@ export async function getSetupInstructions({ soClient: SavedObjectsClientContract; apmServerHost?: string; }): Promise { + const profilerAgent = await fetchFindLatestPackageOrThrow('profiler_agent', { prerelease: true }); const collectorPolicy = await getCollectorPolicy({ packagePolicyClient, soClient }); if (!collectorPolicy) { @@ -46,5 +51,8 @@ export async function getSetupInstructions({ symbolizer: { host: symbolizerHost, }, + profilerAgent: { + version: profilerAgent.version, + }, }; } diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx index 9eb1d9d17be38..87df8261d9ec6 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx @@ -256,9 +256,20 @@ describe('useUserProfileForm', () => { ); - const darkModeButton = testWrapper.find('EuiButtonGroup[data-test-subj="darkModeButton"]'); - expect(darkModeButton).toBeTruthy(); - expect(darkModeButton.getDOMNode()).not.toBeDisabled(); + + const overrideMsg = testWrapper.find('EuiText[data-test-subj="themeOverrideMessage"]'); + expect(overrideMsg).toHaveLength(0); + + const themeMenu = testWrapper.find('EuiKeyPadMenu[data-test-subj="themeMenu"]'); + expect(themeMenu).toHaveLength(1); + + const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); + expect(themeOptions).toHaveLength(3); + themeOptions.forEach((option) => { + expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( + false + ); + }); }); it('should not display if the User is a cloud user', () => { @@ -281,7 +292,7 @@ describe('useUserProfileForm', () => { ); - expect(testWrapper.exists('EuiButtonGroup[data-test-subj="darkModeButton"]')).toBeFalsy(); + expect(testWrapper.exists('EuiButtonGroup[data-test-subj="themeMenu"]')).toBeFalsy(); }); it('should add special toast after submitting form successfully since darkMode requires a refresh', async () => { @@ -314,8 +325,8 @@ describe('useUserProfileForm', () => { const data: UserProfileData = {}; const nonCloudUser = mockAuthenticatedUser({ elastic_cloud_user: false }); - coreStart.settings.client.get.mockReturnValueOnce(true); - coreStart.settings.client.isOverridden.mockReturnValueOnce(true); + coreStart.settings.client.get.mockReturnValue(true); + coreStart.settings.client.isOverridden.mockReturnValue(true); const testWrapper = mount( { ); - const darkModeButton = testWrapper.find('EuiButtonGroup[data-test-subj="darkModeButton"]'); - expect(darkModeButton).toBeTruthy(); - expect(darkModeButton.getDOMNode()).toHaveProperty('disabled'); + const overrideMsg = testWrapper.find('EuiIconTip[data-test-subj="themeOverrideTooltip"]'); + expect(overrideMsg).toHaveLength(1); + + const themeMenu = testWrapper.find('EuiKeyPadMenu[data-test-subj="themeMenu"]'); + expect(themeMenu).toHaveLength(1); + + const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); + expect(themeOptions).toHaveLength(3); + themeOptions.forEach((option) => { + expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( + true + ); + }); }); it('should be disabled if the theme has been set to `darkMode: false` in the config', () => { const data: UserProfileData = {}; const nonCloudUser = mockAuthenticatedUser({ elastic_cloud_user: false }); - coreStart.settings.client.get.mockReturnValueOnce(false); - coreStart.settings.client.isOverridden.mockReturnValueOnce(true); + coreStart.settings.client.get.mockReturnValue(false); + coreStart.settings.client.isOverridden.mockReturnValue(true); const testWrapper = mount( { ); - const darkModeButton = testWrapper.find('EuiButtonGroup[data-test-subj="darkModeButton"]'); - expect(darkModeButton).toBeTruthy(); - expect(darkModeButton.getDOMNode()).toHaveProperty('disabled'); + const overrideMsg = testWrapper.find('EuiIconTip[data-test-subj="themeOverrideTooltip"]'); + expect(overrideMsg).toHaveLength(1); + + const themeMenu = testWrapper.find('EuiKeyPadMenu[data-test-subj="themeMenu"]'); + expect(themeMenu).toHaveLength(1); + + const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); + expect(themeOptions).toHaveLength(3); + themeOptions.forEach((option) => { + expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( + true + ); + }); }); }); }); diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx index 5114fe2f38757..0ffa627ac1a3f 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx @@ -18,6 +18,8 @@ import { EuiFormRow, EuiIcon, EuiIconTip, + EuiKeyPadMenu, + EuiKeyPadMenuItem, EuiPageTemplate_Deprecated as EuiPageTemplate, EuiSpacer, EuiText, @@ -165,6 +167,27 @@ function UserSettingsEditor({ } } + interface ThemeKeyPadItem { + id: string; + label: string; + icon: string; + } + + const themeKeyPadMenuItem = ({ id, label, icon }: ThemeKeyPadItem) => { + return ( + formik.setFieldValue('data.userSettings.darkMode', id)} + > + + + ); + }; + return ( - - + + + + + + + {renderHelpText(isThemeOverridden)} + } fullWidth > - - ), - }, - { - id: 'light', - label: ( - - ), - iconType: 'sun', - }, - { - id: 'dark', - label: ( - - ), - iconType: 'moon', - }, - ]} - onChange={(id: string) => formik.setFieldValue('data.userSettings.darkMode', id)} - isFullWidth - /> + data-test-subj="themeMenu" + checkable={true} + > + {themeKeyPadMenuItem({ + id: '', + label: i18n.translate( + 'xpack.security.accountManagement.userProfile.defaultModeButton', + { + defaultMessage: 'Space default', + } + ), + icon: 'spaces', + })} + {themeKeyPadMenuItem({ + id: 'light', + label: i18n.translate('xpack.security.accountManagement.userProfile.lightModeButton', { + defaultMessage: 'Light', + }), + icon: 'sun', + })} + {themeKeyPadMenuItem({ + id: 'dark', + label: i18n.translate('xpack.security.accountManagement.userProfile.darkModeButton', { + defaultMessage: 'Dark', + }), + icon: 'moon', + })} + ); @@ -912,12 +929,23 @@ export const SaveChangesBottomBar: FunctionComponent = () => { function renderHelpText(isOverridden: boolean) { if (isOverridden) { return ( - - - + + } + /> ); } } diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 14a48f78afe3e..7150d08da713d 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -6,6 +6,7 @@ */ import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; +import type { AddOptionsListControlProps } from '@kbn/controls-plugin/public'; import * as i18n from './translations'; /** @@ -165,6 +166,7 @@ export const ALERT_DETAILS_REDIRECT_PATH = `${ALERTS_PATH}/redirect` as const; export const RULES_PATH = '/rules' as const; export const RULES_LANDING_PATH = `${RULES_PATH}/landing` as const; export const RULES_ADD_PATH = `${RULES_PATH}/add_rules` as const; +export const RULES_UPDATES = `${RULES_PATH}/updates` as const; export const RULES_CREATE_PATH = `${RULES_PATH}/create` as const; export const EXCEPTIONS_PATH = '/exceptions' as const; export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const; @@ -506,19 +508,23 @@ export const MAX_NUMBER_OF_NEW_TERMS_FIELDS = 3; export const BULK_ADD_TO_TIMELINE_LIMIT = 2000; -export const DEFAULT_DETECTION_PAGE_FILTERS = [ +export const DEFAULT_DETECTION_PAGE_FILTERS: Array< + Omit & { persist?: boolean } +> = [ { title: 'Status', fieldName: 'kibana.alert.workflow_status', selectedOptions: ['open'], hideActionBar: true, persist: true, + hideExists: true, }, { title: 'Severity', fieldName: 'kibana.alert.severity', selectedOptions: [], hideActionBar: true, + hideExists: true, }, { title: 'User', diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts index 7d1b6f9086bcd..feb53ffd042b7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts @@ -215,6 +215,7 @@ export class EndpointMetadataGenerator extends BaseDataGenerator { }, }, }, + last_checkin: new Date().toISOString(), }; return merge(hostInfo, overrides); } diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts index d778e1cde027f..eed88ea4d44d2 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts @@ -211,7 +211,7 @@ export async function indexEndpointHostDocs({ await client .index({ index: metadataIndex, - body: hostMetadata, + document: hostMetadata, op_type: 'create', refresh: 'wait_for', }) @@ -225,7 +225,7 @@ export async function indexEndpointHostDocs({ await client .index({ index: policyResponseIndex, - body: hostPolicyResponse, + document: hostPolicyResponse, op_type: 'create', refresh: 'wait_for', }) @@ -281,11 +281,9 @@ export const deleteIndexedEndpointHosts = async ( }; if (indexedData.hosts.length) { - const body = { - query: { - bool: { - filter: [{ terms: { 'agent.id': indexedData.hosts.map((host) => host.agent.id) } }], - }, + const query = { + bool: { + filter: [{ terms: { 'agent.id': indexedData.hosts.map((host) => host.agent.id) } }], }, }; @@ -293,7 +291,7 @@ export const deleteIndexedEndpointHosts = async ( .deleteByQuery({ index: indexedData.metadataIndex, wait_for_completion: true, - body, + query, }) .catch(wrapErrorAndRejectPromise); @@ -302,7 +300,7 @@ export const deleteIndexedEndpointHosts = async ( .deleteByQuery({ index: metadataCurrentIndexPattern, wait_for_completion: true, - body, + query, }) .catch(wrapErrorAndRejectPromise); } @@ -312,19 +310,17 @@ export const deleteIndexedEndpointHosts = async ( .deleteByQuery({ index: indexedData.policyResponseIndex, wait_for_completion: true, - body: { - query: { - bool: { - filter: [ - { - terms: { - 'agent.id': indexedData.policyResponses.map( - (policyResponse) => policyResponse.agent.id - ), - }, + query: { + bool: { + filter: [ + { + terms: { + 'agent.id': indexedData.policyResponses.map( + (policyResponse) => policyResponse.agent.id + ), }, - ], - }, + }, + ], }, }, }) 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 fe1b22644d8e1..1d7a69186421f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -466,6 +466,7 @@ export interface FileUploadMetadata { transithash: { sha256: string; }; + '@timestamp': string; } export type UploadedFileInfo = Pick< diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index aa1881195a0b6..3e48770cd5b39 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -474,7 +474,7 @@ export type PolicyInfo = Immutable<{ }>; // Host Information as returned by the Host Details API. -// NOTE: `HostInfo` type is the original and defined as Immutable. +// NOTE:The `HostInfo` type is the original and defined as Immutable. export interface HostInfoInterface { metadata: HostMetadataInterface; host_status: HostStatus; @@ -485,7 +485,7 @@ export interface HostInfoInterface { */ configured: PolicyInfo; /** - * Last reported running in agent (may lag behind configured) + * Last reported running in agent (might lag behind configured) */ applied: PolicyInfo; }; @@ -494,14 +494,22 @@ export interface HostInfoInterface { */ endpoint: PolicyInfo; }; + /** + * The time when the Elastic Agent associated with this Endpoint host checked in with fleet + * Conceptually the value is the same as Agent['last_checkin'] if present, but we fall back to + * UnitedAgentMetadataPersistedData['united']['endpoint']['metadata']['@timestamp'] + * if `Agent.last_checkin` value is `undefined` + */ + last_checkin: string; } export type HostInfo = Immutable; // Host metadata document streamed up to ES by the Endpoint running on host machines. -// NOTE: `HostMetadata` type is the original and defined as Immutable. If needing to +// NOTE: The `HostMetadata` type is the original and defined as Immutable. If you need to // work with metadata that is not mutable, use `HostMetadataInterface` export type HostMetadata = Immutable; + export interface HostMetadataInterface { '@timestamp': number; event: { diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts new file mode 100644 index 0000000000000..bec5b77de52c9 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BulkInstallPackageInfo } from '@kbn/fleet-plugin/common'; +import type { Rule } from '../../../public/detection_engine/rule_management/logic/types'; +import { createRuleAssetSavedObject } from '../../helpers/rules'; +import { + GO_BACK_TO_RULES_TABLE_BUTTON, + INSTALL_ALL_RULES_BUTTON, + INSTALL_SELECTED_RULES_BUTTON, + RULES_MANAGEMENT_TABLE, + RULES_ROW, + RULES_UPDATES_TABLE, + SELECT_ALL_RULES_ON_PAGE_CHECKBOX, + TOASTER, +} from '../../screens/alerts_detection_rules'; +import { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules'; +import { + getRuleAssets, + createAndInstallMockedPrebuiltRules, +} from '../../tasks/api_calls/prebuilt_rules'; +import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common'; +import { esArchiverResetKibana } from '../../tasks/es_archiver'; +import { login, visitWithoutDateRange } from '../../tasks/login'; +import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; +import { + addElasticRulesButtonClick, + assertRuleUpgradeAvailableAndUpgradeAll, + ruleUpdatesTabClick, +} from '../../tasks/prebuilt_rules'; + +describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => { + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + esArchiverResetKibana(); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + }); + + describe('Installation of prebuilt rules package via Fleet', () => { + beforeEach(() => { + cy.intercept('POST', '/api/fleet/epm/packages/_bulk*').as('installPackage'); + waitForRulesTableToBeLoaded(); + }); + + it('should install package from Fleet in the background', () => { + /* Assert that the package in installed from Fleet by checking that + /* the installSource is "registry", as opposed to "bundle" */ + cy.wait('@installPackage', { + timeout: 60000, + }).then(({ response }) => { + cy.wrap(response?.statusCode).should('eql', 200); + + const packages = response?.body.items.map(({ name, result }: BulkInstallPackageInfo) => ({ + name, + installSource: result.installSource, + })); + + expect(packages.length).to.have.greaterThan(0); + expect(packages).to.deep.include.members([ + { name: 'security_detection_engine', installSource: 'registry' }, + ]); + }); + }); + + it('should install rules from the Fleet package when user clicks on CTA', () => { + /* Retrieve how many rules were installed from the Fleet package */ + cy.wait('@installPackage', { + timeout: 60000, + }).then(() => { + getRuleAssets().then((response) => { + const ruleIds = response.body.hits.hits.map( + (hit: { _source: { ['security-rule']: Rule } }) => hit._source['security-rule'].rule_id + ); + + const numberOfRulesToInstall = new Set(ruleIds).size; + addElasticRulesButtonClick(); + + cy.get(INSTALL_ALL_RULES_BUTTON).click(); + cy.get(TOASTER) + .should('be.visible') + .should('have.text', `${numberOfRulesToInstall} rules installed successfully.`); + }); + }); + }); + }); + + describe('Installation of prebuilt rules', () => { + const RULE_1 = createRuleAssetSavedObject({ + name: 'Test rule 1', + rule_id: 'rule_1', + }); + const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', + }); + beforeEach(() => { + createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2], installToKibana: false }); + waitForRulesTableToBeLoaded(); + }); + + it('should install selected rules when user clicks on Install selected rules', () => { + addElasticRulesButtonClick(); + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + cy.get(INSTALL_SELECTED_RULES_BUTTON).click(); + cy.get(TOASTER).should('be.visible').should('have.text', `2 rules installed successfully.`); + cy.get(GO_BACK_TO_RULES_TABLE_BUTTON).click(); + cy.get(RULES_MANAGEMENT_TABLE).find(RULES_ROW).should('have.length', 2); + cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_1['security-rule'].name); + cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_2['security-rule'].name); + }); + + it('should fail gracefully with toast error message when request to install rules fails', () => { + /* Stub request to force rules installation to fail */ + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform', { + statusCode: 500, + }).as('installPrebuiltRules'); + addElasticRulesButtonClick(); + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + cy.get(INSTALL_SELECTED_RULES_BUTTON).click(); + cy.wait('@installPrebuiltRules'); + cy.get(TOASTER).should('be.visible').should('have.text', 'Rule installation failed'); + }); + }); + + describe('Update of prebuilt rules', () => { + const RULE_ID = 'rule_id'; + const OUTDATED_RULE = createRuleAssetSavedObject({ + name: 'Outdated rule', + rule_id: RULE_ID, + version: 1, + }); + const UPDATED_RULE = createRuleAssetSavedObject({ + name: 'Updated rule', + rule_id: RULE_ID, + version: 2, + }); + beforeEach(() => { + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE] }); + /* Create a second version of the rule, making it available for update */ + createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false }); + waitForRulesTableToBeLoaded(); + reload(); + }); + + it('should update rule succesfully', () => { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( + 'updatePrebuiltRules' + ); + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE); + cy.get(TOASTER).should('be.visible').should('have.text', `1 rule updated successfully.`); + }); + + it('should fail gracefully with toast error message when request to update rules fails', () => { + /* Stub request to force rules update to fail */ + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform', { + statusCode: 500, + }).as('updatePrebuiltRules'); + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE); + cy.get(TOASTER).should('be.visible').should('have.text', 'Rule update failed'); + + /* Assert that the rule has not been updated in the UI */ + cy.get(RULES_UPDATES_TABLE).should('contain', OUTDATED_RULE['security-rule'].name); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_management.cy.ts similarity index 89% rename from x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_management.cy.ts index 024797b7fde78..f36d8f7fa0948 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_management.cy.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { createRuleAssetSavedObject } from '../../helpers/rules'; import { COLLAPSED_ACTION_BTN, ELASTIC_RULES_BTN, - LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN, + ADD_ELASTIC_RULES_BTN, RULES_EMPTY_PROMPT, RULES_MONITORING_TAB, RULES_ROW, @@ -29,13 +30,20 @@ import { waitForRuleToUpdate, } from '../../tasks/alerts_detection_rules'; import { - excessivelyInstallAllPrebuiltRules, + createAndInstallMockedPrebuiltRules, getAvailablePrebuiltRulesCount, } from '../../tasks/api_calls/prebuilt_rules'; -import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; +import { cleanKibana, deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; +const rules = Array.from(Array(5)).map((_, i) => { + return createRuleAssetSavedObject({ + name: `Test rule ${i + 1}`, + rule_id: `rule_${i + 1}`, + }); +}); + describe('Prebuilt rules', () => { before(() => { cleanKibana(); @@ -44,8 +52,9 @@ describe('Prebuilt rules', () => { beforeEach(() => { login(); deleteAlertsAndRules(); + deletePrebuiltRulesAssets(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - excessivelyInstallAllPrebuiltRules(); + createAndInstallMockedPrebuiltRules({ rules }); cy.reload(); waitForPrebuiltDetectionRulesToBeLoaded(); }); @@ -114,10 +123,10 @@ describe('Prebuilt rules', () => { 'have.text', `Elastic rules (${expectedNumberOfRulesAfterDeletion})` ); - cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('have.text', `Add Elastic rules1`); + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules1`); // Navigate to the prebuilt rule installation page - cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click(); + cy.get(ADD_ELASTIC_RULES_BTN).click(); // Click the "Install all rules" button cy.get(INSTALL_ALL_RULES_BUTTON).click(); @@ -144,7 +153,7 @@ describe('Prebuilt rules', () => { selectNumberOfRules(numberOfRulesToBeSelected); deleteSelectedRules(); - cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should( + cy.get(ADD_ELASTIC_RULES_BTN).should( 'have.text', `Add Elastic rules${numberOfRulesToBeSelected}` ); @@ -154,7 +163,7 @@ describe('Prebuilt rules', () => { ); // Navigate to the prebuilt rule installation page - cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click(); + cy.get(ADD_ELASTIC_RULES_BTN).click(); // Click the "Install all rules" button cy.get(INSTALL_ALL_RULES_BUTTON).click(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts new file mode 100644 index 0000000000000..782298088244f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createRuleAssetSavedObject } from '../../helpers/rules'; +import { ADD_ELASTIC_RULES_BTN, RULES_UPDATES_TAB } from '../../screens/alerts_detection_rules'; +import { deleteFirstRule, waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules'; +import { + installAllPrebuiltRulesRequest, + createAndInstallMockedPrebuiltRules, +} from '../../tasks/api_calls/prebuilt_rules'; +import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common'; +import { login, visitWithoutDateRange } from '../../tasks/login'; +import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; + +describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => { + beforeEach(() => { + login(); + /* Make sure persisted rules table state is cleared */ + resetRulesTableState(); + deleteAlertsAndRules(); + + const RULE_1 = createRuleAssetSavedObject({ + name: 'Test rule 1', + rule_id: 'rule_1', + }); + createAndInstallMockedPrebuiltRules({ rules: [RULE_1], installToKibana: false }); + }); + + describe('Rules installation notification when no rules have been installed', () => { + beforeEach(() => { + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + }); + + it('should notify user about prebuilt rules available for installation', () => { + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + }); + }); + + describe('No notifications', () => { + it('should display no install or update notifications when latest rules are installed', () => { + /* Install current available rules */ + installAllPrebuiltRulesRequest(); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + + /* Assert that there are no installation or update notifications */ + /* Add Elastic Rules button and Rule Upgrade tabs should not contain a number badge */ + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', 'Add Elastic rules'); + cy.get(RULES_UPDATES_TAB).should('have.text', 'Rule Updates'); + }); + }); + + describe('Rule installation notification when at least one rule already installed', () => { + beforeEach(() => { + installAllPrebuiltRulesRequest(); + /* Create new rule assets with a different rule_id as the one that was */ + /* installed before in order to trigger the installation notification */ + const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', + }); + const RULE_3 = createRuleAssetSavedObject({ + name: 'Test rule 3', + rule_id: 'rule_3', + }); + + createAndInstallMockedPrebuiltRules({ rules: [RULE_2, RULE_3], installToKibana: false }); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + }); + + it('should notify user about prebuilt rules package available for installation', () => { + const numberOfAvailableRules = 2; + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + cy.get(ADD_ELASTIC_RULES_BTN).should( + 'have.text', + `Add Elastic rules${numberOfAvailableRules}` + ); + }); + + it('should notify user a rule is again available for installation if it is deleted', () => { + /* Install available rules, assert that the notification is gone */ + /* then delete one rule and assert that the notification is back */ + installAllPrebuiltRulesRequest(); + reload(); + deleteFirstRule(); + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + }); + }); + + describe('Rule update notification', () => { + beforeEach(() => { + installAllPrebuiltRulesRequest(); + /* Create new rule asset with the same rule_id as the one that was installed */ + /* but with a higher version, in order to trigger the update notification */ + const UPDATED_RULE = createRuleAssetSavedObject({ + name: 'Test rule 1.1 (updated)', + rule_id: 'rule_1', + version: 2, + }); + createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false }); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + reload(); + }); + + it('should notify user about prebuilt rules package available for update', () => { + cy.get(RULES_UPDATES_TAB).should('be.visible'); + cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts index 43d2445b318b0..848fd94ad94ed 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { createRuleAssetSavedObject } from '../../helpers/rules'; import { SELECTED_RULES_NUMBER_LABEL, SELECT_ALL_RULES_BTN, @@ -17,17 +18,32 @@ import { import { excessivelyInstallAllPrebuiltRules, getAvailablePrebuiltRulesCount, + createAndInstallMockedPrebuiltRules, } from '../../tasks/api_calls/prebuilt_rules'; import { cleanKibana } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; -// TODO: See https://github.com/elastic/kibana/issues/154694 -describe.skip('Rules selection', () => { - beforeEach(() => { +const RULE_1 = createRuleAssetSavedObject({ + name: 'Test rule 1', + rule_id: 'rule_1', +}); +const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', +}); + +describe('Rules selection', () => { + before(() => { cleanKibana(); + }); + + beforeEach(() => { login(); + /* Create and install two mock rules */ + createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] }); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + waitForPrebuiltDetectionRulesToBeLoaded(); }); it('should correctly update the selection label when rules are individually selected and unselected', () => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index 6e77002c945dd..acff1f8acb426 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -6,13 +6,13 @@ */ import { encode } from '@kbn/rison'; +import type { FilterItemObj } from '../../../../public/common/components/filter_group/types'; import { getNewRule } from '../../../objects/rule'; import { CONTROL_FRAMES, CONTROL_FRAME_TITLE, CONTROL_POPOVER, FILTER_GROUP_CHANGED_BANNER, - OPTION_IGNORED, OPTION_LIST_LABELS, OPTION_LIST_VALUES, OPTION_SELECTABLE, @@ -29,7 +29,6 @@ import { formatPageFilterSearchParam } from '../../../../common/utils/format_pag import { closePageFilterPopover, markAcknowledgedFirstAlert, - openFirstAlert, openPageFilterPopover, resetFilters, selectCountTable, @@ -38,7 +37,7 @@ import { waitForAlerts, waitForPageFilters, } from '../../../tasks/alerts'; -import { ALERTS_COUNT, ALERTS_REFRESH_BTN } from '../../../screens/alerts'; +import { ALERTS_COUNT, ALERTS_REFRESH_BTN, EMPTY_ALERT_TABLE } from '../../../screens/alerts'; import { kqlSearch, navigateFromHeaderTo } from '../../../tasks/security_header'; import { ALERTS, CASES } from '../../../screens/security_header'; import { @@ -84,7 +83,9 @@ const customFilters = [ title: 'Rule Name', }, ]; -const assertFilterControlsWithFilterObject = (filterObject = DEFAULT_DETECTION_PAGE_FILTERS) => { +const assertFilterControlsWithFilterObject = ( + filterObject: FilterItemObj[] = DEFAULT_DETECTION_PAGE_FILTERS +) => { cy.get(CONTROL_FRAMES).should((sub) => { expect(sub.length).eq(filterObject.length); }); @@ -97,18 +98,16 @@ const assertFilterControlsWithFilterObject = (filterObject = DEFAULT_DETECTION_P filterObject.forEach((filter, idx) => { cy.get(OPTION_LIST_VALUES(idx)).should((sub) => { - expect(sub.text().replace(',', '')).satisfy((txt: string) => { - return txt.startsWith( - filter.selectedOptions && filter.selectedOptions.length > 0 - ? filter.selectedOptions.join('') - : '' - ); - }); + const selectedOptionsText = + filter.selectedOptions && filter.selectedOptions.length > 0 + ? filter.selectedOptions.join('') + : ''; + expect(sub.text().replace(',', '').replace(' ', '')).to.have.string(selectedOptionsText); }); }); }; -describe('Detections : Page Filters', () => { +describe(`Detections : Page Filters`, () => { before(() => { cleanKibana(); createRule(getNewRule({ rule_id: 'custom_rule_filters' })); @@ -130,6 +129,9 @@ describe('Detections : Page Filters', () => { login(); visit(ALERTS_URL); waitForAlerts(); + }); + + afterEach(() => { resetFilters(); }); @@ -182,10 +184,11 @@ describe('Detections : Page Filters', () => { it('Page filters are loaded with custom values provided in the URL', () => { const NEW_FILTERS = DEFAULT_DETECTION_PAGE_FILTERS.filter((item) => item.persist).map( (filter) => { - if (filter.title === 'Status') { - filter.selectedOptions = ['open', 'acknowledged']; - } - return filter; + return { + ...filter, + selectedOptions: + filter.title === 'Status' ? ['open', 'acknowledged'] : filter.selectedOptions, + }; } ); @@ -231,40 +234,39 @@ describe('Detections : Page Filters', () => { cy.get(FILTER_GROUP_CHANGED_BANNER).should('be.visible'); }); - it(`Alert list is updated when the alerts are updated`, () => { - // mark status of one alert to be acknowledged - selectCountTable(); - cy.get(ALERTS_COUNT) - .invoke('text') - .then((noOfAlerts) => { - const originalAlertCount = noOfAlerts.split(' ')[0]; - markAcknowledgedFirstAlert(); - waitForAlerts(); - cy.get(OPTION_LIST_VALUES(0)).click(); - cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible').trigger('click'); - cy.get(ALERTS_COUNT) - .invoke('text') - .should((newAlertCount) => { - expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10) - 1)); - }); - }); + context('with data modificiation', () => { + after(() => { + cleanKibana(); + createRule(getNewRule({ rule_id: 'custom_rule_filters' })); + }); - // cleanup - // revert the changes so that data does not change for further tests. - // It would make sure that tests can run in any order. - cy.get(OPTION_SELECTABLE(0, 'open')).trigger('click'); - togglePageFilterPopover(0); - openFirstAlert(); - waitForAlerts(); + it(`Alert list is updated when the alerts are updated`, () => { + // mark status of one alert to be acknowledged + selectCountTable(); + cy.get(ALERTS_COUNT) + .invoke('text') + .then((noOfAlerts) => { + const originalAlertCount = noOfAlerts.split(' ')[0]; + markAcknowledgedFirstAlert(); + waitForAlerts(); + cy.get(OPTION_LIST_VALUES(0)).click(); + cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible').trigger('click'); + cy.get(ALERTS_COUNT) + .invoke('text') + .should((newAlertCount) => { + expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10) - 1)); + }); + }); + }); }); it(`URL is updated when filters are updated`, () => { cy.on('url:changed', (urlString) => { const NEW_FILTERS = DEFAULT_DETECTION_PAGE_FILTERS.map((filter) => { - if (filter.title === 'Severity') { - filter.selectedOptions = ['high']; - } - return filter; + return { + ...filter, + selectedOptions: filter.title === 'Severity' ? ['high'] : filter.selectedOptions, + }; }); const expectedVal = encode(formatPageFilterSearchParam(NEW_FILTERS)); expect(urlString).to.contain.text(expectedVal); @@ -331,7 +333,7 @@ describe('Detections : Page Filters', () => { afterEach(() => { resetFilters(); }); - it('should recover from invalide kql Query result', () => { + it('should recover from invalid kql Query result', () => { // do an invalid search // kqlSearch('\\'); @@ -350,7 +352,7 @@ describe('Detections : Page Filters', () => { waitForPageFilters(); togglePageFilterPopover(0); cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found'); - cy.get(OPTION_IGNORED(0, 'open')).should('be.visible'); + cy.get(EMPTY_ALERT_TABLE).should('be.visible'); }); it('should take filters into account', () => { @@ -362,7 +364,7 @@ describe('Detections : Page Filters', () => { waitForPageFilters(); togglePageFilterPopover(0); cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found'); - cy.get(OPTION_IGNORED(0, 'open')).should('be.visible'); + cy.get(EMPTY_ALERT_TABLE).should('be.visible'); }); it('should take timeRange into account', () => { const startDateWithZeroAlerts = 'Jan 1, 2002 @ 00:00:00.000'; @@ -375,7 +377,7 @@ describe('Detections : Page Filters', () => { waitForPageFilters(); togglePageFilterPopover(0); cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found'); - cy.get(OPTION_IGNORED(0, 'open')).should('be.visible'); + cy.get(EMPTY_ALERT_TABLE).should('be.visible'); }); }); it('Number fields are not visible in field edit panel', () => { diff --git a/x-pack/plugins/security_solution/cypress/helpers/rules.ts b/x-pack/plugins/security_solution/cypress/helpers/rules.ts index b8ad823d669cf..a9a6594301384 100644 --- a/x-pack/plugins/security_solution/cypress/helpers/rules.ts +++ b/x-pack/plugins/security_solution/cypress/helpers/rules.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import dateMath from '@kbn/datemath'; import moment from 'moment'; +import type { PrebuiltRuleAsset } from '../../server/lib/detection_engine/prebuilt_rules'; +import { getPrebuiltRuleMock } from '../../server/lib/detection_engine/prebuilt_rules/mocks'; import type { ThreatArray } from '../../common/detection_engine/rule_schema'; @@ -77,3 +78,21 @@ export const convertHistoryStartToSize = (relativeTime: string) => { return relativeTime; } }; + +/** + * A helper function to create a rule asset saved object (type: security-rule) + * + * @param overrideParams Params to override the default mock + * @returns Created rule asset saved object + */ +export const createRuleAssetSavedObject = (overrideParams: Partial) => ({ + 'security-rule': { + ...getPrebuiltRuleMock(), + ...overrideParams, + }, + type: 'security-rule', + references: [], + coreMigrationVersion: '8.6.0', + updated_at: '2022-11-01T12:56:39.717Z', + created_at: '2022-11-01T12:56:39.717Z', +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index a4383b48d2dd0..2a83a70338e5e 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -59,12 +59,21 @@ export const INTEGRATIONS_POPOVER = '[data-test-subj="IntegrationsDisplayPopover export const INTEGRATIONS_POPOVER_TITLE = '[data-test-subj="IntegrationsPopoverTitle"]'; -export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; +export const ADD_ELASTIC_RULES_BTN = '[data-test-subj="addElasticRulesButton"]'; -export const LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN = '[data-test-subj="loadPrebuiltRulesBtn"]'; +export const ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN = + '[data-test-subj="add-elastc-rules-empty-empty-prompt-button"]'; export const INSTALL_ALL_RULES_BUTTON = '[data-test-subj="installAllRulesButton"]'; +export const INSTALL_SELECTED_RULES_BUTTON = '[data-test-subj="installSelectedRulesButton"]'; + +export const UPGRADE_ALL_RULES_BUTTON = '[data-test-subj="upgradeAllRulesButton"]'; + +export const UPGRADE_SELECTED_RULES_BUTTON = '[data-test-subj="upgradeSelectedRulesButton"]'; + +export const GO_BACK_TO_RULES_TABLE_BUTTON = '[data-test-subj="addRulesGoBackToRulesTableBtn"]'; + export const RULES_TABLE_INITIAL_LOADING_INDICATOR = '[data-test-subj="initialLoadingPanelAllRulesTable"]'; @@ -90,10 +99,14 @@ export const RULES_MANAGEMENT_TAB = '[data-test-subj="navigation-management"]'; export const RULES_MONITORING_TAB = '[data-test-subj="navigation-monitoring"]'; +export const RULES_UPDATES_TAB = '[data-test-subj="navigation-updates"]'; + export const RULES_MANAGEMENT_TABLE = '[data-test-subj="rules-management-table"]'; export const RULES_MONITORING_TABLE = '[data-test-subj="rules-monitoring-table"]'; +export const RULES_UPDATES_TABLE = '[data-test-subj="rules-upgrades-table"]'; + export const RULES_ROW = '.euiTableRow'; export const SEVERITY = '[data-test-subj="severity"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts b/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts index ab56bee03a34b..a4c069c2e2467 100644 --- a/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts +++ b/x-pack/plugins/security_solution/cypress/screens/common/filter_group.ts @@ -21,6 +21,8 @@ export const OPTION_LIST_LABELS = '.controlFrame__labelToolTip'; export const OPTION_LIST_VALUES = (idx: number) => `[data-test-subj="optionsList-control-${idx}"]`; +export const OPTION_LIST_CLEAR_BTN = '.presentationUtil__floatingActions [aria-label="Clear"]'; + export const OPTION_LIST_NUMBER_OFF = '.euiFilterButton__notification'; export const OPTION_LISTS_LOADING = '.optionsList--filterBtnWrapper .euiLoadingSpinner'; @@ -48,7 +50,11 @@ export const DETECTION_PAGE_FILTERS_LOADING = '.securityPageWrapper .controlFram export const DETECTION_PAGE_FILTER_GROUP_LOADING = '[data-test-subj="filter-group__loading"]'; -export const DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU = '[data-test-subj="filter-group__context"]'; +export const DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU_BTN = + '[data-test-subj="filter-group__context"]'; + +export const DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU = + '[data-test-subj="filter-group__context-menu"]'; export const DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON = '[data-test-subj="filter-group__context--reset"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 67d61925fa164..0e6a152414763 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -6,6 +6,7 @@ */ import { encode } from '@kbn/rison'; +import { recurse } from 'cypress-recurse'; import { formatPageFilterSearchParam } from '../../common/utils/format_page_filter_search_param'; import { TOP_N_CONTAINER } from '../screens/network/flows'; import { @@ -63,14 +64,14 @@ import { } from '../screens/alerts_details'; import { FIELD_INPUT } from '../screens/exceptions'; import { + CONTROL_FRAME_TITLE, DETECTION_PAGE_FILTERS_LOADING, - DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU, DETECTION_PAGE_FILTER_GROUP_LOADING, DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON, DETECTION_PAGE_FILTER_GROUP_WRAPPER, - OPTION_LISTS_EXISTS, OPTION_LISTS_LOADING, OPTION_LIST_VALUES, + OPTION_LIST_CLEAR_BTN, OPTION_SELECTABLE, } from '../screens/common/filter_group'; import { LOADING_SPINNER } from '../screens/common/page'; @@ -78,6 +79,7 @@ import { ALERTS_URL } from '../urls/navigation'; import { FIELDS_BROWSER_BTN } from '../screens/rule_details'; import type { FilterItemObj } from '../../public/common/components/filter_group/types'; import { visit } from './login'; +import { openFilterGroupContextMenu } from './common/filter_group'; export const addExceptionFromFirstAlert = () => { expandFirstAlertActions(); @@ -175,23 +177,21 @@ export const closePageFilterPopover = (filterIndex: number) => { cy.get(OPTION_LIST_VALUES(filterIndex)).should('not.have.class', 'euiFilterButton-isSelected'); }; -export const clearAllSelections = () => { - cy.get(OPTION_LISTS_EXISTS).click({ force: true }); - - cy.get(OPTION_LISTS_EXISTS).then(($el) => { - if ($el.attr('aria-checked', 'false')) { - // check it - $el.click(); - } - // uncheck it - $el.click(); - }); +export const clearAllSelections = (filterIndex: number) => { + recurse( + () => { + cy.get(CONTROL_FRAME_TITLE).eq(filterIndex).realHover(); + return cy.get(OPTION_LIST_CLEAR_BTN).eq(filterIndex); + }, + ($el) => $el.is(':visible') + ); + cy.get(OPTION_LIST_CLEAR_BTN).eq(filterIndex).should('be.visible').trigger('click'); }; export const selectPageFilterValue = (filterIndex: number, ...values: string[]) => { refreshAlertPageFilter(); + clearAllSelections(filterIndex); openPageFilterPopover(filterIndex); - clearAllSelections(); values.forEach((value) => { cy.get(OPTION_SELECTABLE(filterIndex, value)).click({ force: true }); }); @@ -407,9 +407,10 @@ export const waitForPageFilters = () => { }; export const resetFilters = () => { - cy.get(DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU).click({ force: true }); - cy.get(DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON).click({ force: true }); + openFilterGroupContextMenu(); + cy.get(DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON).trigger('click'); waitForPageFilters(); + cy.log('Resetting filters complete'); }; export const parseAlertsCountToInt = (count: string | number) => diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 7f4a8fcacd0b1..97dbdad4506a9 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -13,7 +13,6 @@ import { DELETE_RULE_ACTION_BTN, DELETE_RULE_BULK_BTN, RULES_SELECTED_TAG, - LOAD_PREBUILT_RULES_BTN, RULES_TABLE_INITIAL_LOADING_INDICATOR, RULES_TABLE_AUTOREFRESH_INDICATOR, RULE_CHECKBOX, @@ -65,6 +64,7 @@ import { DUPLICATE_WITH_EXCEPTIONS_OPTION, DUPLICATE_WITH_EXCEPTIONS_WITHOUT_EXPIRED_OPTION, TOASTER_CLOSE_ICON, + ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN, } from '../screens/alerts_detection_rules'; import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules'; import { EUI_CHECKBOX } from '../screens/common/controls'; @@ -336,7 +336,7 @@ export const waitForRulesTableToBeRefreshed = () => { export const waitForPrebuiltDetectionRulesToBeLoaded = () => { cy.log('Wait for prebuilt rules to be loaded'); - cy.get(LOAD_PREBUILT_RULES_BTN, { timeout: 300000 }).should('not.exist'); + cy.get(ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN, { timeout: 300000 }).should('not.exist'); cy.get(RULES_MANAGEMENT_TABLE).should('exist'); cy.get(RULES_TABLE_REFRESH_INDICATOR).should('not.exist'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts index 424426e91d793..1a079cb43ec20 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts @@ -5,7 +5,10 @@ * 2.0. */ +import { ELASTIC_SECURITY_RULE_ID } from '../../../common/detection_engine/constants'; import type { PrePackagedRulesStatusResponse } from '../../../public/detection_engine/rule_management/logic/types'; +import { getPrebuiltRuleWithExceptionsMock } from '../../../server/lib/detection_engine/prebuilt_rules/mocks'; +import { createRuleAssetSavedObject } from '../../helpers/rules'; export const getPrebuiltRulesStatus = () => { return cy.request({ @@ -15,6 +18,18 @@ export const getPrebuiltRulesStatus = () => { }); }; +export const SAMPLE_PREBUILT_RULE = createRuleAssetSavedObject({ + ...getPrebuiltRuleWithExceptionsMock(), + rule_id: ELASTIC_SECURITY_RULE_ID, + tags: ['test-tag-1'], + enabled: true, +}); + +/* Install all prebuilt rules available as security-rule saved objects + * Use in combination with `preventPrebuiltRulesPackageInstallation` and + * `createNewRuleAsset` to create mocked prebuilt rules and install only those + * instead of all rules available in the `security_detection_engine` package + */ export const installAllPrebuiltRulesRequest = () => { return cy.request({ method: 'POST', @@ -58,3 +73,131 @@ export const excessivelyInstallAllPrebuiltRules = () => { waitTillPrebuiltRulesReadyToInstall(); installAllPrebuiltRulesRequest(); }; + +export const createNewRuleAsset = ({ + index = '.kibana_security_solution', + rule = SAMPLE_PREBUILT_RULE, +}: { + index?: string; + rule?: typeof SAMPLE_PREBUILT_RULE; +}) => { + const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_doc/security-rule:${ + rule['security-rule'].rule_id + }`; + cy.waitUntil( + () => { + return cy + .request({ + method: 'PUT', + url, + headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' }, + failOnStatusCode: false, + body: rule, + }) + .then((response) => response.status === 200); + }, + { interval: 500, timeout: 12000 } + ); +}; + +export const bulkCreateRuleAssets = ({ + index = '.kibana_security_solution', + rules = [SAMPLE_PREBUILT_RULE], +}: { + index?: string; + rules?: Array; +}) => { + const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_bulk`; + + const bulkIndexRequestBody = rules.reduce((body, rule) => { + const indexOperation = { + index: { + _index: index, + _id: rule['security-rule'].rule_id, + }, + }; + + const documentData = JSON.stringify(rule); + + return body.concat(JSON.stringify(indexOperation), '\n', documentData, '\n'); + }, ''); + + cy.request({ + method: 'PUT', + url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_mapping`, + body: { + dynamic: true, + }, + headers: { + 'Content-Type': 'application/json', + }, + }); + + cy.waitUntil( + () => { + return cy + .request({ + method: 'POST', + url, + headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' }, + failOnStatusCode: false, + body: bulkIndexRequestBody, + }) + .then((response) => response.status === 200); + }, + { interval: 500, timeout: 12000 } + ); +}; + +export const getRuleAssets = (index: string | undefined = '.kibana_security_solution') => { + const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search?size=10000`; + return cy.request({ + method: 'GET', + url, + headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' }, + failOnStatusCode: false, + body: { + query: { + term: { type: { value: 'security-rule' } }, + }, + }, + }); +}; + +/* Prevent the installation of the `security_detection_engine` package from Fleet +/* by intercepting the request and returning a mock empty object as response +/* Used primarily to prevent the unwanted installation of "real" prebuilt rules +/* during e2e tests, and allow for manual installation of mock rules instead. */ +export const preventPrebuiltRulesPackageInstallation = () => { + cy.intercept('POST', '/api/fleet/epm/packages/_bulk*', {}); +}; + +/** + * Prevent the installation of the `security_detection_engine` package from Fleet. + * The create a `security-rule` asset for each rule provided in the `rules` array. + * Optionally install the rules to Kibana, with a flag defaulting to true + * Explicitly set the `installToKibana` flag to false in cases when needing to + * make mock rules available for installation or update, but do those operations manually + * + * * @param {Array} rules - Rule assets to be created and optionally installed + * + * * @param {string} installToKibana - Flag to decide whether to install the rules as 'alerts' SO. Defaults to true. + */ +export const createAndInstallMockedPrebuiltRules = ({ + rules, + installToKibana = true, +}: { + rules?: Array; + installToKibana?: boolean; +}) => { + cy.log('Install prebuilt rules'); + preventPrebuiltRulesPackageInstallation(); + // TODO: use this bulk method once the issue with Cypress is fixed + // bulkCreateRuleAssets({ rules }); + rules?.forEach((rule) => { + createNewRuleAsset({ rule }); + }); + if (installToKibana) { + installAllPrebuiltRulesRequest(); + } +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index 08518c2049f1c..1767b70fe33fe 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -218,6 +218,27 @@ export const deleteConnectors = () => { }); }; +export const deletePrebuiltRulesAssets = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'security-rule', + }, + }, + ], + }, + }, + }, + }); +}; + export const postDataView = (dataSource: string) => { rootRequest({ method: 'POST', diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts b/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts index b19cf2586ff78..d3b1c5857bb45 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common/filter_group.ts @@ -5,8 +5,9 @@ * 2.0. */ +import { recurse } from 'cypress-recurse'; import { - DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU, + DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU_BTN, DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON, FILTER_GROUP_ADD_CONTROL, FILTER_GROUP_CONTEXT_EDIT_CONTROLS, @@ -23,11 +24,18 @@ import { OPTION_LISTS_LOADING, FILTER_GROUP_CONTEXT_DISCARD_CHANGES, FILTER_GROUP_CONTROL_ACTION_EDIT, + DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU, } from '../../screens/common/filter_group'; import { waitForPageFilters } from '../alerts'; export const openFilterGroupContextMenu = () => { - cy.get(DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU).click(); + recurse( + () => { + cy.get(DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU_BTN).click(); + return cy.get(DETECTION_PAGE_FILTER_GROUP_CONTEXT_MENU).should(Cypress._.noop); + }, + ($el) => $el.length === 1 + ); }; export const waitForFilterGroups = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts new file mode 100644 index 0000000000000..5035b50acaff9 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.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 { RULES_ADD_PATH, RULES_UPDATES } from '../../common/constants'; +import { + ADD_ELASTIC_RULES_BTN, + RULES_ROW, + RULES_UPDATES_TAB, + RULES_UPDATES_TABLE, + UPGRADE_ALL_RULES_BUTTON, +} from '../screens/alerts_detection_rules'; +import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules'; + +export const addElasticRulesButtonClick = () => { + cy.get(ADD_ELASTIC_RULES_BTN).click(); + cy.location('pathname').should('include', RULES_ADD_PATH); +}; + +export const ruleUpdatesTabClick = () => { + cy.get(RULES_UPDATES_TAB).click(); + cy.location('pathname').should('include', RULES_UPDATES); +}; + +export const assertRuleUpgradeAvailableAndUpgradeAll = (rule: typeof SAMPLE_PREBUILT_RULE) => { + cy.get(RULES_UPDATES_TABLE).find(RULES_ROW).should('have.length', 1); + cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); + cy.get(UPGRADE_ALL_RULES_BUTTON).click(); + cy.wait('@updatePrebuiltRules'); +}; diff --git a/x-pack/plugins/security_solution/cypress/tsconfig.json b/x-pack/plugins/security_solution/cypress/tsconfig.json index bbe065934c066..b1ba42799a478 100644 --- a/x-pack/plugins/security_solution/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/cypress/tsconfig.json @@ -36,6 +36,7 @@ "@kbn/cases-plugin", "@kbn/data-plugin", "@kbn/core-http-common", - "@kbn/data-views-plugin" + "@kbn/data-views-plugin", + "@kbn/fleet-plugin", ] } diff --git a/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.test.tsx b/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.test.tsx index bb64a2a2bdac6..87c920e787895 100644 --- a/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.test.tsx +++ b/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.test.tsx @@ -17,6 +17,7 @@ import { createStore } from '../../../common/store'; import { createShowTopNCellActionFactory } from './show_top_n'; import React from 'react'; import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; jest.mock('../../../common/lib/kibana'); @@ -45,7 +46,7 @@ describe('createShowTopNCellActionFactory', () => { value: 'the-value', field: { name: 'user.name', - type: 'keyword', + type: KBN_FIELD_TYPES.STRING, aggregatable: true, searchable: true, }, @@ -71,35 +72,54 @@ describe('createShowTopNCellActionFactory', () => { }); describe('isCompatible', () => { - it('should return true if everything is okay', async () => { - expect(await showTopNAction.isCompatible(context)).toEqual(true); - }); - - it('should return false if field esType does not support aggregations', async () => { + it('should return false if field is not aggregatable', async () => { expect( await showTopNAction.isCompatible({ ...context, data: [ { - field: { ...context.data[0].field, esTypes: ['text'] }, + field: { ...context.data[0].field, aggregatable: false }, }, ], }) ).toEqual(false); }); - it('should return false if field is not aggregatable', async () => { + it('should return false if field is nested', async () => { expect( await showTopNAction.isCompatible({ ...context, data: [ { - field: { ...context.data[0].field, aggregatable: false }, + field: { ...context.data[0].field, subType: { nested: { path: 'test_path' } } }, }, ], }) ).toEqual(false); }); + + describe.each([ + { type: KBN_FIELD_TYPES.STRING, expectedValue: true }, + { type: KBN_FIELD_TYPES.BOOLEAN, expectedValue: true }, + { type: KBN_FIELD_TYPES.NUMBER, expectedValue: true }, + { type: KBN_FIELD_TYPES.IP, expectedValue: true }, + { type: KBN_FIELD_TYPES.DATE, expectedValue: false }, + { type: KBN_FIELD_TYPES.GEO_SHAPE, expectedValue: false }, + { type: KBN_FIELD_TYPES.IP_RANGE, expectedValue: false }, + ])('lens supported KBN types', ({ type, expectedValue }) => { + it(`should return ${expectedValue} when type is ${type}`, async () => { + expect( + await showTopNAction.isCompatible({ + ...context, + data: [ + { + field: { ...context.data[0].field, type }, + }, + ], + }) + ).toEqual(expectedValue); + }); + }); }); describe('execute', () => { diff --git a/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.tsx b/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.tsx index 0e1c419b0449e..3491f0ffcc620 100644 --- a/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/actions/show_top_n/cell_action/show_top_n.tsx @@ -12,7 +12,7 @@ import { Router } from '@kbn/shared-ux-router'; import { i18n } from '@kbn/i18n'; import { createCellActionFactory, type CellActionTemplate } from '@kbn/cell-actions'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -import { ES_FIELD_TYPES } from '@kbn/field-types'; +import { isDataViewFieldSubtypeNested } from '@kbn/es-query'; import { KibanaContextProvider } from '../../../common/lib/kibana'; import { APP_NAME, DEFAULT_DARK_MODE } from '../../../../common/constants'; import type { SecurityAppStore } from '../../../common/store'; @@ -21,6 +21,7 @@ import { TopNAction } from '../show_top_n_component'; import type { StartServices } from '../../../types'; import type { SecurityCellAction } from '../../types'; import { SecurityCellActionType } from '../../constants'; +import { isLensSupportedType } from '../../../common/utils/lens'; const SHOW_TOP = (fieldName: string) => i18n.translate('xpack.securitySolution.actions.showTopTooltip', { @@ -29,7 +30,6 @@ const SHOW_TOP = (fieldName: string) => }); const ICON = 'visBarVertical'; -const UNSUPPORTED_ES_FIELD_TYPES = [ES_FIELD_TYPES.DATE, ES_FIELD_TYPES.TEXT]; export const createShowTopNCellActionFactory = createCellActionFactory( ({ @@ -51,9 +51,8 @@ export const createShowTopNCellActionFactory = createCellActionFactory( return ( data.length === 1 && fieldHasCellActions(field.name) && - (field.esTypes ?? []).every( - (esType) => !UNSUPPORTED_ES_FIELD_TYPES.includes(esType as ES_FIELD_TYPES) - ) && + isLensSupportedType(field.type) && + !isDataViewFieldSubtypeNested(field) && !!field.aggregatable ); }, diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/breadcrumbs.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/breadcrumbs.ts index 16bd30db6680d..f02bc6f4ac32c 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/breadcrumbs.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; -import type { GetSecuritySolutionUrl } from '../common/components/link_to'; -import type { RouteSpyState } from '../common/utils/route/types'; +import type { GetTrailingBreadcrumbs } from '../common/components/navigation/breadcrumbs/types'; -export const getTrailingBreadcrumbs = ( - params: RouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = (params, getSecuritySolutionUrl) => { const breadcrumbs = []; if (params.state?.ruleName) { diff --git a/x-pack/plugins/security_solution/public/common/breadcrumbs/breadcrumbs.ts b/x-pack/plugins/security_solution/public/common/breadcrumbs/breadcrumbs.ts new file mode 100644 index 0000000000000..8b61c7c2be10a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/breadcrumbs/breadcrumbs.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 { BehaviorSubject } from 'rxjs'; +import type { BreadcrumbsNav } from './types'; + +// Used to update the breadcrumbsNav$ observable internally. +const breadcrumbsNavUpdater$ = new BehaviorSubject({ + leading: [], + trailing: [], +}); + +// The observable can be exposed by the plugin contract. +export const breadcrumbsNav$ = breadcrumbsNavUpdater$.asObservable(); + +export const updateBreadcrumbsNav = (breadcrumbsNav: BreadcrumbsNav) => { + breadcrumbsNavUpdater$.next(breadcrumbsNav); +}; diff --git a/x-pack/plugins/security_solution/public/common/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/breadcrumbs/index.ts new file mode 100644 index 0000000000000..7caff04239860 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/breadcrumbs/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { BreadcrumbsNav } from './types'; +export { updateBreadcrumbsNav, breadcrumbsNav$ } from './breadcrumbs'; diff --git a/x-pack/plugins/security_solution/public/common/breadcrumbs/types.ts b/x-pack/plugins/security_solution/public/common/breadcrumbs/types.ts new file mode 100644 index 0000000000000..ab3aff52ec60c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/breadcrumbs/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ChromeBreadcrumb } from '@kbn/core/public'; + +export interface BreadcrumbsNav { + trailing: ChromeBreadcrumb[]; + leading: ChromeBreadcrumb[]; +} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index a282a5c58a77c..4457a669b19ae 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -86,7 +86,6 @@ interface Props { data: TimelineEventsDetailsItem[]; detailsEcsData: Ecs | null; id: string; - indexName: string; isAlert: boolean; isDraggable?: boolean; rawEventData: object | undefined; @@ -154,7 +153,6 @@ const EventDetailsComponent: React.FC = ({ data, detailsEcsData, id, - indexName, isAlert, isDraggable, rawEventData, @@ -235,7 +233,6 @@ const EventDetailsComponent: React.FC = ({ contextId={scopeId} data={data} eventId={id} - indexName={indexName} scopeId={scopeId} handleOnEventClosed={handleOnEventClosed} isReadOnly={isReadOnly} @@ -328,7 +325,6 @@ const EventDetailsComponent: React.FC = ({ scopeId, data, id, - indexName, handleOnEventClosed, isReadOnly, renderer, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx index 497c3c5083b54..b33f777dcff41 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx @@ -42,22 +42,12 @@ interface Props { data: TimelineEventsDetailsItem[]; eventId: string; handleOnEventClosed: () => void; - indexName: string; scopeId: string; isReadOnly?: boolean; } export const Overview = React.memo( - ({ - browserFields, - contextId, - data, - eventId, - handleOnEventClosed, - indexName, - scopeId, - isReadOnly, - }) => { + ({ browserFields, contextId, data, eventId, handleOnEventClosed, scopeId, isReadOnly }) => { const statusData = useMemo(() => { const item = find({ field: SIGNAL_STATUS_FIELD_NAME, category: 'kibana' }, data); return ( @@ -128,7 +118,6 @@ export const Overview = React.memo( eventId={eventId} contextId={contextId} enrichedFieldInfo={statusData} - indexName={indexName} scopeId={scopeId} handleOnEventClosed={handleOnEventClosed} /> diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx index 5a2f27e7a98c4..28a0a117bacc1 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx @@ -45,7 +45,6 @@ const props = { fields: {}, }, }, - indexName: '.internal.alerts-security.alerts-default-000001', scopeId: 'alerts-page', handleOnEventClosed: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx index 748b2d6a4779f..ba88adaf99a48 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx @@ -24,13 +24,12 @@ interface StatusPopoverButtonProps { eventId: string; contextId: string; enrichedFieldInfo: EnrichedFieldInfoWithValues; - indexName: string; scopeId: string; handleOnEventClosed: () => void; } export const StatusPopoverButton = React.memo( - ({ eventId, contextId, enrichedFieldInfo, indexName, scopeId, handleOnEventClosed }) => { + ({ eventId, contextId, enrichedFieldInfo, scopeId, handleOnEventClosed }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); const closePopover = useCallback(() => setIsPopoverOpen(false), []); @@ -51,7 +50,6 @@ export const StatusPopoverButton = React.memo( closePopover: closeAfterAction, eventId, scopeId, - indexName, alertStatus: enrichedFieldInfo.values[0] as Status, refetch: refetchGlobalQuery, }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 97c72e0c5e975..b145fa81345a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -496,7 +496,6 @@ const StatefulEventsViewerComponent: React.FC = { + hideExclude: true, + hideSort: true, + hidePanelTitles: true, + placeholder: '', + ignoreParentSettings: { + ignoreValidations: true, + }, +}; + +export const TIMEOUTS = { + /* because of recent changes in controls-plugin debounce time may not be needed + * still keeping the config for some time for any recent changes + * */ + FILTER_UPDATES_DEBOUNCE_TIME: 0, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/context_menu.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/context_menu.tsx index b7dc2f6f32457..2e77a40a70a80 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/context_menu.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/context_menu.tsx @@ -7,7 +7,7 @@ import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; -import { TEST_IDS } from './constants'; +import { COMMON_OPTIONS_LIST_CONTROL_INPUTS, TEST_IDS } from './constants'; import { useFilterGroupInternalContext } from './hooks/use_filters'; import { CONTEXT_MENU_RESET, @@ -60,10 +60,7 @@ export const FilterGroupContextMenu = () => { const control = initialControls[counter]; await controlGroup?.addOptionsListControl({ controlId: String(counter), - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, // option List controls will handle an invalid dataview // & display an appropriate message dataViewId: dataViewId ?? '', @@ -139,6 +136,9 @@ export const FilterGroupContextMenu = () => { closePopover={toggleContextMenu} panelPaddingSize="none" anchorPosition="downLeft" + panelProps={{ + 'data-test-subj': TEST_IDS.CONTEXT_MENU.MENU, + }} > diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx index 5600d231df19a..7d3c961473fa9 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx @@ -26,7 +26,7 @@ import { OPTIONS_LIST_CONTROL } from '@kbn/controls-plugin/common'; import { initialInputData, sampleOutputData } from './mocks/data'; import { createStore } from '../../store'; import { useGetInitialUrlParamValue } from '../../utils/global_query_string/helpers'; -import { TEST_IDS } from './constants'; +import { COMMON_OPTIONS_LIST_CONTROL_INPUTS, TEST_IDS } from './constants'; import { controlGroupFilterInputMock$, controlGroupFilterOutputMock$, @@ -304,7 +304,7 @@ describe(' Filter Group Component ', () => { ); }); - it('should save controls successfully', async () => { + it('should not rebuild controls while saving controls when controls are in desired order', async () => { render(); updateControlGroupInputMock(initialInputData as ControlGroupInput); await openContextMenu(); @@ -314,7 +314,9 @@ describe(' Filter Group Component ', () => { const newInputData = { ...initialInputData, panels: { + // status as persistent controls is first in the position with order as 0 '0': initialInputData.panels['0'], + '1': initialInputData.panels['1'], }, } as ControlGroupInput; @@ -329,10 +331,51 @@ describe(' Filter Group Component ', () => { // edit model gone expect(screen.queryAllByTestId(TEST_IDS.SAVE_CONTROL)).toHaveLength(0); - // check if upsert was called correctely - expect(controlGroupMock.addOptionsListControl.mock.calls.length).toBe(1); + // check if upsert was called correctly + expect(controlGroupMock.addOptionsListControl.mock.calls.length).toBe(0); + }); + }); + + it('should rebuild and save controls successfully when controls are not in desired order', async () => { + render(); + updateControlGroupInputMock(initialInputData as ControlGroupInput); + await openContextMenu(); + fireEvent.click(screen.getByTestId(TEST_IDS.CONTEXT_MENU.EDIT)); + + // modify controls + const newInputData = { + ...initialInputData, + panels: { + '0': { + ...initialInputData.panels['0'], + // status is second in position. + // this will force the rebuilding of controls + order: 1, + }, + '1': { + ...initialInputData.panels['1'], + order: 0, + }, + }, + } as ControlGroupInput; + + updateControlGroupInputMock(newInputData); + + // clear any previous calls to the API + controlGroupMock.addOptionsListControl.mockClear(); + + fireEvent.click(screen.getByTestId(TEST_IDS.SAVE_CONTROL)); + + await waitFor(() => { + // edit model gone + expect(screen.queryAllByTestId(TEST_IDS.SAVE_CONTROL)).toHaveLength(0); + + // check if upsert was called correctly + expect(controlGroupMock.addOptionsListControl.mock.calls.length).toBe(2); + // field id is not required to be passed when creating a control + const { id, ...expectedInputData } = initialInputData.panels['0'].explicitInput; expect(controlGroupMock.addOptionsListControl.mock.calls[0][0]).toMatchObject({ - ...initialInputData.panels['0'].explicitInput, + ...expectedInputData, }); }); }); @@ -363,17 +406,18 @@ describe(' Filter Group Component ', () => { await waitFor(() => { // edit model gone expect(screen.queryAllByTestId(TEST_IDS.SAVE_CONTROL)).toHaveLength(0); - // check if upsert was called correctely + // check if upsert was called correctly expect(controlGroupMock.addOptionsListControl.mock.calls.length).toBe(2); expect(controlGroupMock.addOptionsListControl.mock.calls[0][0]).toMatchObject({ - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, ...DEFAULT_DETECTION_PAGE_FILTERS[0], }); + + // field id is not required to be passed when creating a control + const { id, ...expectedInputData } = initialInputData.panels['3'].explicitInput; + expect(controlGroupMock.addOptionsListControl.mock.calls[1][0]).toMatchObject({ - ...initialInputData.panels['3'].explicitInput, + ...expectedInputData, }); }); }); @@ -595,18 +639,12 @@ describe(' Filter Group Component ', () => { updateControlGroupInputMock(initialInputData as ControlGroupInput); expect(controlGroupMock.addOptionsListControl.mock.calls.length).toBe(2); expect(controlGroupMock.addOptionsListControl.mock.calls[0][1]).toMatchObject({ - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, ...DEFAULT_DETECTION_PAGE_FILTERS[0], }); expect(controlGroupMock.addOptionsListControl.mock.calls[1][1]).toMatchObject({ - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, fieldName: 'abc', }); diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx index 52629cc58c2b9..dd1f9109a4e1e 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx @@ -6,11 +6,7 @@ */ import type { Filter } from '@kbn/es-query'; -import type { - ControlInputTransform, - ControlPanelState, - OptionsListEmbeddableInput, -} from '@kbn/controls-plugin/common'; +import type { ControlInputTransform } from '@kbn/controls-plugin/common'; import { OPTIONS_LIST_CONTROL } from '@kbn/controls-plugin/common'; import type { ControlGroupInput, @@ -18,6 +14,7 @@ import type { ControlGroupOutput, ControlGroupContainer, ControlGroupRendererProps, + DataControlInput, } from '@kbn/controls-plugin/public'; import { ControlGroupRenderer } from '@kbn/controls-plugin/public'; import type { PropsWithChildren } from 'react'; @@ -26,7 +23,7 @@ import { ViewMode } from '@kbn/embeddable-plugin/public'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import type { Subscription } from 'rxjs'; import styled from 'styled-components'; -import { cloneDeep, debounce, isEqual } from 'lodash'; +import { debounce, isEqual, isEqualWith } from 'lodash'; import type { ControlGroupCreationOptions, FieldFilterPredicate, @@ -43,11 +40,16 @@ import { useControlGroupSyncToLocalStorage } from './hooks/use_control_group_syn import { useViewEditMode } from './hooks/use_view_edit_mode'; import { FilterGroupContextMenu } from './context_menu'; import { AddControl, SaveControls } from './buttons'; -import { getFilterItemObjListFromControlInput } from './utils'; +import { + getFilterControlsComparator, + getFilterItemObjListFromControlInput, + mergeControls, + reorderControlsWithDefaultControls, +} from './utils'; import { FiltersChangedBanner } from './filters_changed_banner'; import { FilterGroupContext } from './filter_group_context'; import { NUM_OF_CONTROLS } from './config'; -import { TEST_IDS } from './constants'; +import { COMMON_OPTIONS_LIST_CONTROL_INPUTS, TEST_IDS, TIMEOUTS } from './constants'; import { URL_PARAM_ARRAY_EXCEPTION_MSG } from './translations'; import { convertToBuildEsQuery } from '../../lib/kuery'; @@ -79,6 +81,15 @@ const FilterGroupComponent = (props: PropsWithChildren) => { const filterChangedSubscription = useRef(); const inputChangedSubscription = useRef(); + const initialControlsObj = useMemo( + () => + initialControls.reduce>((prev, current) => { + prev[current.fieldName] = current; + return prev; + }, {}), + [initialControls] + ); + const [controlGroup, setControlGroup] = useState(); const localStoragePageFilterKey = useMemo( @@ -128,7 +139,9 @@ const FilterGroupComponent = (props: PropsWithChildren) => { const storedControlGroupInput = getStoredControlInput(); if (storedControlGroupInput) { const panelsFormatted = getFilterItemObjListFromControlInput(storedControlGroupInput); - if (!isEqual(panelsFormatted, param)) { + if ( + !isEqualWith(panelsFormatted, param, getFilterControlsComparator('fieldName', 'title')) + ) { setShowFiltersChangedBanner(true); switchToEditMode(); } @@ -203,8 +216,12 @@ const FilterGroupComponent = (props: PropsWithChildren) => { ); const handleOutputFilterUpdates = useCallback( - ({ filters: newFilters }: ControlGroupOutput) => { + ({ filters: newFilters, embeddableLoaded }: ControlGroupOutput) => { + const haveAllEmbeddablesLoaded = Object.values(embeddableLoaded).every((v) => + Boolean(v ?? true) + ); if (isEqual(currentFiltersRef.current, newFilters)) return; + if (!haveAllEmbeddablesLoaded) return; if (onFilterChange) onFilterChange(newFilters ?? []); currentFiltersRef.current = newFilters ?? []; }, @@ -212,7 +229,7 @@ const FilterGroupComponent = (props: PropsWithChildren) => { ); const debouncedFilterUpdates = useMemo( - () => debounce(handleOutputFilterUpdates, 500), + () => debounce(handleOutputFilterUpdates, TIMEOUTS.FILTER_UPDATES_DEBOUNCE_TIME), [handleOutputFilterUpdates] ); @@ -253,53 +270,35 @@ const FilterGroupComponent = (props: PropsWithChildren) => { * * */ - const localInitialControls = cloneDeep(initialControls).filter( - (control) => control.persist === true - ); - let resultControls = [] as FilterItemObj[]; - - let overridingControls = initialUrlParam; - if (!initialUrlParam || initialUrlParam.length === 0) { - // if nothing is found in URL Param.. read from local storage - const storedControlGroupInput = getStoredControlInput(); - if (storedControlGroupInput) { - const urlParamsFromLocalStorage: FilterItemObj[] = - getFilterItemObjListFromControlInput(storedControlGroupInput); - - overridingControls = urlParamsFromLocalStorage; - } + const controlsFromURL = initialUrlParam ?? []; + let controlsFromLocalStorage: FilterItemObj[] = []; + const storedControlGroupInput = getStoredControlInput(); + if (storedControlGroupInput) { + controlsFromLocalStorage = getFilterItemObjListFromControlInput(storedControlGroupInput); } + let overridingControls = mergeControls({ + controlsWithPriority: [controlsFromURL, controlsFromLocalStorage], + defaultControlsObj: initialControlsObj, + }); if (!overridingControls || overridingControls.length === 0) return initialControls; - // if initialUrlParam Exists... replace localInitialControls with what was provided in the Url - if (overridingControls && !urlDataApplied.current) { - if (localInitialControls.length > 0) { - localInitialControls.forEach((persistControl) => { - const doesPersistControlAlreadyExist = overridingControls?.some( - (control) => control.fieldName === persistControl.fieldName - ); - - if (!doesPersistControlAlreadyExist) { - resultControls.push(persistControl); - } - }); - } - - resultControls = [ - ...resultControls, - ...overridingControls.map((item) => ({ - fieldName: item.fieldName, - title: item.title, - selectedOptions: item.selectedOptions ?? [], - existsSelected: item.existsSelected ?? false, - exclude: item.exclude, - })), - ]; - } + overridingControls = overridingControls.map((item) => { + return { + // give default value to params which are coming from the URL + fieldName: item.fieldName, + title: item.title, + selectedOptions: item.selectedOptions ?? [], + existsSelected: item.existsSelected ?? false, + exclude: item.exclude, + }; + }); - return resultControls; - }, [initialUrlParam, initialControls, getStoredControlInput]); + return reorderControlsWithDefaultControls({ + controls: overridingControls, + defaultControls: initialControls, + }); + }, [initialUrlParam, initialControls, getStoredControlInput, initialControlsObj]); const fieldFilterPredicate: FieldFilterPredicate = useCallback((f) => f.type !== 'number', []); @@ -325,10 +324,7 @@ const FilterGroupComponent = (props: PropsWithChildren) => { finalControls.forEach((control, idx) => { addOptionsListControl(initialInput, { controlId: String(idx), - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, // option List controls will handle an invalid dataview // & display an appropriate message dataViewId: dataViewId ?? '', @@ -376,48 +372,29 @@ const FilterGroupComponent = (props: PropsWithChildren) => { }, [controlGroup, switchToViewMode, getStoredControlInput, hasPendingChanges]); const upsertPersistableControls = useCallback(async () => { - const persistableControls = initialControls.filter((control) => control.persist === true); - if (persistableControls.length > 0) { - const currentPanels = Object.values(controlGroup?.getInput().panels ?? []) as Array< - ControlPanelState - >; - const orderedPanels = currentPanels.sort((a, b) => a.order - b.order); - let filterControlsDeleted = false; - for (const control of persistableControls) { - const controlExists = currentPanels.some( - (currControl) => control.fieldName === currControl.explicitInput.fieldName - ); - if (!controlExists) { - // delete current controls - if (!filterControlsDeleted) { - controlGroup?.updateInput({ panels: {} }); - filterControlsDeleted = true; - } - - // add persitable controls - await controlGroup?.addOptionsListControl({ - title: control.title, - hideExclude: true, - hideSort: true, - hidePanelTitles: true, - placeholder: '', - // option List controls will handle an invalid dataview - // & display an appropriate message - dataViewId: dataViewId ?? '', - selectedOptions: control.selectedOptions, - ...control, - }); - } - } + if (!controlGroup) return; + const currentPanels = getFilterItemObjListFromControlInput(controlGroup.getInput()); + + const reorderedControls = reorderControlsWithDefaultControls({ + controls: currentPanels, + defaultControls: initialControls, + }); + + if (!isEqualWith(reorderedControls, currentPanels, getFilterControlsComparator('fieldName'))) { + // reorder only if fields are in different order + // or not same. + controlGroup?.updateInput({ panels: {} }); - for (const panel of orderedPanels) { - if (panel.explicitInput.fieldName) - await controlGroup?.addOptionsListControl({ - selectedOptions: [], - fieldName: panel.explicitInput.fieldName, - dataViewId: dataViewId ?? '', - ...panel.explicitInput, - }); + for (const control of reorderedControls) { + await controlGroup?.addOptionsListControl({ + title: control.title, + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, + // option List controls will handle an invalid dataview + // & display an appropriate message + dataViewId: dataViewId ?? '', + selectedOptions: control.selectedOptions, + ...control, + }); } } }, [controlGroup, dataViewId, initialControls]); @@ -428,23 +405,36 @@ const FilterGroupComponent = (props: PropsWithChildren) => { setShowFiltersChangedBanner(false); }, [switchToViewMode, upsertPersistableControls]); - const newControlInputTranform: ControlInputTransform = (newInput, controlType) => { - // for any new controls, we want to avoid - // default placeholder - if (controlType === OPTIONS_LIST_CONTROL) { - return { - ...newInput, - placeholder: '', - }; - } - return newInput; - }; + const newControlInputTranform: ControlInputTransform = useCallback( + (newInput, controlType) => { + // for any new controls, we want to avoid + // default placeholder + let result = newInput; + if (controlType === OPTIONS_LIST_CONTROL) { + result = { + ...newInput, + ...COMMON_OPTIONS_LIST_CONTROL_INPUTS, + }; + + if ((newInput as DataControlInput).fieldName in initialControlsObj) { + result = { + ...result, + ...initialControlsObj[(newInput as DataControlInput).fieldName], + // title should not be overridden by the initial controls, hence the hardcoding + title: newInput.title ?? result.title, + }; + } + } + return result; + }, + [initialControlsObj] + ); const addControlsHandler = useCallback(() => { controlGroup?.openAddDataControlFlyout({ controlInputTransform: newControlInputTranform, }); - }, [controlGroup]); + }, [controlGroup, newControlInputTranform]); return ( void; } -export type FilterItemObj = Omit & - Pick; +export type FilterItemObj = Omit & { + /* + * Determines the present and order of a control + * + * */ + persist?: boolean; +}; export type FilterGroupHandler = ControlGroupContainer; export type FilterGroupProps = { dataViewId: string | null; onFilterChange?: (newFilters: Filter[]) => void; - initialControls: Array; + initialControls: FilterItemObj[]; spaceId: string; onInit?: (controlGroupHandler: FilterGroupHandler | undefined) => void; } & Pick; diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/utils.test.ts b/x-pack/plugins/security_solution/public/common/components/filter_group/utils.test.ts index edcffd7502df9..f2f860710ac84 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/utils.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/utils.test.ts @@ -6,9 +6,64 @@ */ import type { ControlGroupInput } from '@kbn/controls-plugin/common'; -import { getFilterItemObjListFromControlInput } from './utils'; +import { + getFilterItemObjListFromControlInput, + mergeControls, + reorderControlsWithDefaultControls, + getFilterControlsComparator, +} from './utils'; import { initialInputData } from './mocks/data'; +import type { FilterItemObj } from './types'; +import { isEqualWith } from 'lodash'; +const defaultControls: FilterItemObj[] = [ + { + fieldName: 'first', + hideActionBar: true, + selectedOptions: ['val1', 'val2'], + }, + + { + fieldName: 'second', + hideActionBar: true, + selectedOptions: ['val1', 'val2'], + persist: true, + }, +]; + +const firstControlsSet: FilterItemObj[] = [ + { + fieldName: 'first', + selectedOptions: ['firstVal'], + }, +]; + +const secondControlsSet: FilterItemObj[] = [ + { + fieldName: 'first', + selectedOptions: ['secondVal1', 'secondVal2'], + existsSelected: true, + }, + { + fieldName: 'second', + hideActionBar: false, + exclude: true, + }, +]; + +const thirdControlsSet: FilterItemObj[] = [ + { + fieldName: 'new', + selectedOptions: [], + }, +]; + +const emptyControlSet: FilterItemObj[] = []; + +const defaultControlsObj = defaultControls.reduce((prev, current) => { + prev[current.fieldName] = current; + return prev; +}, {} as Record); describe('utils', () => { describe('getFilterItemObjListFromControlOutput', () => { it('should return ordered filterItem where passed in order', () => { @@ -61,4 +116,172 @@ describe('utils', () => { }); }); }); + + describe('mergeControls', () => { + it('should return first controls set when it is not empty', () => { + const result = mergeControls({ + controlsWithPriority: [firstControlsSet, secondControlsSet], + defaultControlsObj, + }); + + const expectedResult = [ + { + fieldName: 'first', + selectedOptions: ['firstVal'], + hideActionBar: true, + }, + ]; + + expect(result).toMatchObject(expectedResult); + }); + + it('should return second controls set when first one is empty', () => { + const result = mergeControls({ + controlsWithPriority: [emptyControlSet, secondControlsSet], + defaultControlsObj, + }); + + const expectedResult = [ + { + fieldName: 'first', + selectedOptions: ['secondVal1', 'secondVal2'], + hideActionBar: true, + existsSelected: true, + }, + { + fieldName: 'second', + selectedOptions: ['val1', 'val2'], + hideActionBar: false, + exclude: true, + persist: true, + }, + ]; + + expect(result).toMatchObject(expectedResult); + }); + + it('should return controls as it is when default control for a field does not exist', () => { + const result = mergeControls({ + controlsWithPriority: [emptyControlSet, emptyControlSet, thirdControlsSet], + defaultControlsObj, + }); + const expectedResult = thirdControlsSet; + expect(result).toMatchObject(expectedResult); + }); + + it('should return default controls if no priority controls are given', () => { + const result = mergeControls({ + controlsWithPriority: [emptyControlSet, emptyControlSet, emptyControlSet], + defaultControlsObj, + }); + + expect(result).toBeUndefined(); + }); + }); + + describe('reorderControls', () => { + it('should add persist controls in order if they are not available in the given controls', () => { + const newControlsSet: FilterItemObj[] = [ + { + fieldName: 'new', + }, + ]; + + const result = reorderControlsWithDefaultControls({ + controls: newControlsSet, + defaultControls, + }); + + const expectedResult = [ + { + fieldName: 'second', + hideActionBar: true, + selectedOptions: ['val1', 'val2'], + persist: true, + }, + { + fieldName: 'new', + }, + ]; + + expect(result).toMatchObject(expectedResult); + }); + it('should change controls order if they are available in the given controls', () => { + const newControlsSet: FilterItemObj[] = [ + { + fieldName: 'new', + }, + { + fieldName: 'second', + selectedOptions: ['val2'], + hideActionBar: false, + }, + { + fieldName: 'first', + selectedOptions: [], + }, + ]; + + const expectedResult = [ + { + fieldName: 'second', + selectedOptions: ['val2'], + hideActionBar: false, + persist: true, + }, + { + fieldName: 'new', + }, + { + fieldName: 'first', + selectedOptions: [], + hideActionBar: true, + }, + ]; + + const result = reorderControlsWithDefaultControls({ + controls: newControlsSet, + defaultControls, + }); + + expect(result).toMatchObject(expectedResult); + }); + }); + + describe('getFilterControlsComparator', () => { + it('should return true when controls are equal and and list of field is empty', () => { + const comparator = getFilterControlsComparator(); + const result = isEqualWith(defaultControls, defaultControls, comparator); + + expect(result).toBe(true); + }); + it('should return false when arrays of different length', () => { + const comparator = getFilterControlsComparator(); + const result = isEqualWith(defaultControls, thirdControlsSet, comparator); + + expect(result).toBe(false); + }); + it('should return true when given set of fields match ', () => { + const comparator = getFilterControlsComparator('fieldName'); + const result = isEqualWith(defaultControls, secondControlsSet, comparator); + + expect(result).toBe(true); + }); + it("should return false when given set of fields don't match ", () => { + const comparator = getFilterControlsComparator('fieldName', 'selectedOptions'); + const result = isEqualWith(defaultControls, secondControlsSet, comparator); + expect(result).toBe(false); + }); + + it('should return true when comparing empty set of filter controls', () => { + const comparator = getFilterControlsComparator('fieldName', 'selectedOptions'); + const result = isEqualWith([], [], comparator); + expect(result).toBe(true); + }); + it('should return false when comparing one empty and one non-empty set of filter controls', () => { + const comparator = getFilterControlsComparator('fieldName', 'selectedOptions'); + const result = isEqualWith(defaultControls, [], comparator); + expect(result).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/utils.ts b/x-pack/plugins/security_solution/public/common/components/filter_group/utils.ts index 6523ff61b2060..c005621831d5b 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/utils.ts @@ -11,6 +11,9 @@ import type { OptionsListEmbeddableInput, } from '@kbn/controls-plugin/common'; +import { isEmpty, isEqual, pick } from 'lodash'; +import type { FilterItemObj } from './types'; + export const getPanelsInOrderFromControlsInput = (controlInput: ControlGroupInput) => { const panels = controlInput.panels; @@ -21,7 +24,7 @@ export const getFilterItemObjListFromControlInput = (controlInput: ControlGroupI const panels = getPanelsInOrderFromControlsInput(controlInput); return panels.map((panel) => { const { - explicitInput: { fieldName, selectedOptions, title, existsSelected, exclude }, + explicitInput: { fieldName, selectedOptions, title, existsSelected, exclude, hideActionBar }, } = panel as ControlPanelState; return { @@ -30,6 +33,115 @@ export const getFilterItemObjListFromControlInput = (controlInput: ControlGroupI title, existsSelected: existsSelected ?? false, exclude: exclude ?? false, + hideActionBar: hideActionBar ?? false, }; }); }; + +interface MergableControlsArgs { + /* + * Set of controls that need be merged with priority + * Set of controls with lower index take priority over the next one. + * + * Final set of controls is merged with the defaulControls + * + */ + controlsWithPriority: FilterItemObj[][]; + defaultControlsObj: Record; +} + +/* + * mergeControls merges controls based on priority with the default controls + * + * @return undefined if all provided controls are empty + * */ +export const mergeControls = ({ + controlsWithPriority, + defaultControlsObj, +}: MergableControlsArgs) => { + const highestPriorityControlSet = controlsWithPriority.find((control) => !isEmpty(control)); + + return highestPriorityControlSet?.map((singleControl) => { + if (singleControl.fieldName in defaultControlsObj) { + return { + ...defaultControlsObj[singleControl.fieldName], + ...singleControl, + }; + } + return singleControl; + }); +}; + +interface ReorderControlsArgs { + /* + * Ordered Controls + * + * */ + controls: FilterItemObj[]; + /* + * default controls in order + * */ + defaultControls: FilterItemObj[]; +} + +/** + * reorderControlsWithPersistentControls reorders the controls such that controls which + * are persistent in default controls should be upserted in given order + * + * */ +export const reorderControlsWithDefaultControls = (args: ReorderControlsArgs) => { + const { controls, defaultControls } = args; + const controlsObject = controls.reduce((prev, current) => { + prev[current.fieldName] = current; + return prev; + }, {} as Record); + + const defaultControlsObj = defaultControls.reduce((prev, current) => { + prev[current.fieldName] = current; + return prev; + }, {} as Record); + + const resultDefaultControls: FilterItemObj[] = defaultControls + .filter((defaultControl) => defaultControl.persist) + .map((defaultControl) => { + return { + ...defaultControl, + ...(controlsObject[defaultControl.fieldName] ?? {}), + }; + }); + + const resultNonPersitantControls = controls + .filter( + // filter out persisting controls since we have already taken + // in account above + (control) => !defaultControlsObj[control.fieldName]?.persist + ) + .map((control) => ({ + // insert some default properties from default controls + // irrespective of whether they are persistent or not. + ...(defaultControlsObj[control.fieldName] ?? {}), + ...control, + })); + + return [...resultDefaultControls, ...resultNonPersitantControls]; +}; + +/* + * getFilterControlsComparator provides a comparator that can be used with `isEqualWith` to compare + * 2 instances of FilterItemObj + * + * */ +export const getFilterControlsComparator = + (...fieldsToCompare: Array) => + (filterItemObject1: FilterItemObj[], filterItemObject2: FilterItemObj[]) => { + if (filterItemObject1.length !== filterItemObject2.length) return false; + const filterItemObjectWithSelectedKeys1 = filterItemObject1.map((v) => { + return pick(v, fieldsToCompare); + }); + + const filterItemObjectWithSelectedKeys2 = filterItemObject2.map((v) => { + return pick(v, fieldsToCompare); + }); + + return isEqual(filterItemObjectWithSelectedKeys1, filterItemObjectWithSelectedKeys2); + }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/get_breadcrumbs_for_page.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/get_breadcrumbs_for_page.ts deleted file mode 100644 index 325f490a351b1..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/get_breadcrumbs_for_page.ts +++ /dev/null @@ -1,33 +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 { ChromeBreadcrumb } from '@kbn/core/public'; -import { SecurityPageName } from '../../../../app/types'; -import { APP_NAME } from '../../../../../common/constants'; -import { getAppLandingUrl } from '../../link_to/redirect_to_landing'; - -import type { GetSecuritySolutionUrl } from '../../link_to'; -import { getAncestorLinksInfo } from '../../../links'; - -export const getLeadingBreadcrumbsForSecurityPage = ( - pageName: SecurityPageName, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): [ChromeBreadcrumb, ...ChromeBreadcrumb[]] => { - const landingPath = getSecuritySolutionUrl({ deepLinkId: SecurityPageName.landing }); - - const siemRootBreadcrumb: ChromeBreadcrumb = { - text: APP_NAME, - href: getAppLandingUrl(landingPath), - }; - - const breadcrumbs: ChromeBreadcrumb[] = getAncestorLinksInfo(pageName).map(({ title, id }) => ({ - text: title, - href: getSecuritySolutionUrl({ deepLinkId: id }), - })); - - return [siemRootBreadcrumb, ...breadcrumbs]; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts deleted file mode 100644 index 3d683bccd1b76..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ /dev/null @@ -1,549 +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 '../../../mock/match_media'; -import { encodeIpv6 } from '../../../lib/helpers'; -import { getBreadcrumbsForRoute, useBreadcrumbs } from '.'; -import { HostsTableType } from '../../../../explore/hosts/store/model'; -import type { RouteSpyState } from '../../../utils/route/types'; -import { NetworkRouteType } from '../../../../explore/network/pages/navigation/types'; -import { AdministrationSubTab } from '../../../../management/types'; -import { renderHook } from '@testing-library/react-hooks'; -import { TestProviders } from '../../../mock'; -import type { GetSecuritySolutionUrl } from '../../link_to'; -import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; -import { links } from '../../../links/app_links'; -import { updateAppLinks } from '../../../links'; -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; -import { AlertDetailRouteType } from '../../../../detections/pages/alert_details/types'; -import { UsersTableType } from '../../../../explore/users/store/model'; -import { UpsellingService } from '../../../lib/upsellings'; - -const mockUseRouteSpy = jest.fn(); -jest.mock('../../../utils/route/use_route_spy', () => ({ - useRouteSpy: () => mockUseRouteSpy(), -})); - -const getMockObject = ( - pageName: SecurityPageName, - pathName: string, - detailName: string | undefined -): RouteSpyState => { - switch (pageName) { - case SecurityPageName.hosts: - return { - detailName, - pageName, - pathName, - search: '', - tabName: HostsTableType.authentications, - }; - - case SecurityPageName.users: - return { - detailName, - pageName, - pathName, - search: '', - tabName: UsersTableType.allUsers, - }; - - case SecurityPageName.network: - return { - detailName, - pageName, - pathName, - search: '', - tabName: NetworkRouteType.flows, - }; - - case SecurityPageName.administration: - return { - detailName, - pageName, - pathName, - search: '', - tabName: AdministrationSubTab.endpoints, - }; - - case SecurityPageName.alerts: - return { - detailName, - pageName, - pathName, - search: '', - tabName: AlertDetailRouteType.summary, - }; - - default: - return { - detailName, - pageName, - pathName, - search: '', - } as RouteSpyState; - } -}; - -// The string returned is different from what getSecuritySolutionUrl returns, but does not matter for the purposes of this test. -const getSecuritySolutionUrl: GetSecuritySolutionUrl = ({ - deepLinkId, - path, -}: { - deepLinkId?: string; - path?: string; - absolute?: boolean; -}) => `${APP_UI_ID}${deepLinkId ? `/${deepLinkId}` : ''}${path ?? ''}`; - -const mockSetBreadcrumbs = jest.fn(); -jest.mock('../../../lib/kibana/kibana_react', () => { - return { - useKibana: () => ({ - services: { - chrome: { - setBreadcrumbs: mockSetBreadcrumbs, - }, - application: { - navigateToApp: jest.fn(), - getUrlForApp: (appId: string, options?: { path?: string; deepLinkId?: boolean }) => - `${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`, - }, - }, - }), - }; -}); - -const hostName = 'siem-kibana'; - -const ipv4 = '192.0.2.255'; -const ipv6 = '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff'; -const ipv6Encoded = encodeIpv6(ipv6); - -const securityBreadcrumb = { - href: 'securitySolutionUI/get_started', - text: 'Security', -}; - -const hostsBreadcrumb = { - href: 'securitySolutionUI/hosts', - text: 'Hosts', -}; - -const networkBreadcrumb = { - text: 'Network', - href: 'securitySolutionUI/network', -}; - -const exploreBreadcrumb = { - href: 'securitySolutionUI/explore', - text: 'Explore', -}; - -const rulesLandingBreadcrumb = { - text: 'Rules', - href: 'securitySolutionUI/rules-landing', -}; - -const rulesBreadcrumb = { - text: 'SIEM Rules', - href: 'securitySolutionUI/rules', -}; - -const exceptionsBreadcrumb = { - text: 'Shared Exception Lists', - href: 'securitySolutionUI/exceptions', -}; - -const settingsBreadcrumb = { - text: 'Settings', - href: 'securitySolutionUI/administration', -}; - -describe('Navigation Breadcrumbs', () => { - beforeAll(() => { - updateAppLinks(links, { - experimentalFeatures: allowedExperimentalValues, - capabilities: { - navLinks: {}, - management: {}, - catalogue: {}, - actions: { show: true, crud: true }, - siem: { - show: true, - crud: true, - }, - }, - upselling: new UpsellingService(), - }); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('getBreadcrumbsForRoute', () => { - it('should return Overview breadcrumbs when supplied overview pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.overview, '/', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - { - href: 'securitySolutionUI/dashboards', - text: 'Dashboards', - }, - { - href: '', - text: 'Overview', - }, - ]); - }); - - it('should return Host breadcrumbs when supplied hosts pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.hosts, '/', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - exploreBreadcrumb, - hostsBreadcrumb, - { - href: '', - text: 'Authentications', - }, - ]); - }); - - it('should return Network breadcrumbs when supplied network pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.network, '/', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - exploreBreadcrumb, - networkBreadcrumb, - { - text: 'Flows', - href: '', - }, - ]); - }); - - it('should return Timelines breadcrumbs when supplied timelines pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.timelines, '/', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - { - text: 'Timelines', - href: '', - }, - ]); - }); - - it('should return Host Details breadcrumbs when supplied a pathname with hostName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.hosts, '/', hostName), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - exploreBreadcrumb, - hostsBreadcrumb, - { - text: 'siem-kibana', - href: 'securitySolutionUI/hosts/name/siem-kibana', - }, - { text: 'Authentications', href: '' }, - ]); - }); - - it('should return IP Details breadcrumbs when supplied pathname with ipv4', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.network, '/', ipv4), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - exploreBreadcrumb, - networkBreadcrumb, - { - text: ipv4, - href: `securitySolutionUI/network/ip/${ipv4}/source/flows`, - }, - { text: 'Flows', href: '' }, - ]); - }); - - it('should return IP Details breadcrumbs when supplied pathname with ipv6', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.network, '/', ipv6Encoded), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - exploreBreadcrumb, - networkBreadcrumb, - { - text: ipv6, - href: `securitySolutionUI/network/ip/${ipv6Encoded}/source/flows`, - }, - { text: 'Flows', href: '' }, - ]); - }); - - it('should return Alerts breadcrumbs when supplied alerts pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.alerts, '/alerts', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - { - text: 'Alerts', - href: 'securitySolutionUI/alerts', - }, - { - text: 'Summary', - href: '', - }, - ]); - }); - - it('should return Exceptions breadcrumbs when supplied exceptions pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.exceptions, '/exceptions', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - { - text: 'Shared Exception Lists', - href: '', - }, - ]); - }); - - it('should return Rules breadcrumbs when supplied rules pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.rules, '/rules', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - { - ...rulesBreadcrumb, - href: '', - }, - ]); - }); - - it('should return Rules breadcrumbs when supplied rules Creation pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.rules, '/rules/create', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - rulesBreadcrumb, - { - text: 'Create', - href: '', - }, - ]); - }); - - it('should return Rules breadcrumbs when supplied rules Details pageName', () => { - const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; - const mockRuleName = 'ALERT_RULE_NAME'; - const breadcrumbs = getBreadcrumbsForRoute( - { - ...getMockObject(SecurityPageName.rules, `/rules/id/${mockDetailName}`, undefined), - detailName: mockDetailName, - state: { - ruleName: mockRuleName, - }, - }, - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - rulesBreadcrumb, - { - text: mockRuleName, - href: `securitySolutionUI/rules/id/${mockDetailName}`, - }, - { - text: 'Deleted rule', - href: '', - }, - ]); - }); - - it('should return Rules breadcrumbs when supplied rules Edit pageName', () => { - const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; - const mockRuleName = 'ALERT_RULE_NAME'; - const breadcrumbs = getBreadcrumbsForRoute( - { - ...getMockObject(SecurityPageName.rules, `/rules/id/${mockDetailName}/edit`, undefined), - detailName: mockDetailName, - state: { - ruleName: mockRuleName, - }, - }, - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - rulesBreadcrumb, - { - text: 'ALERT_RULE_NAME', - href: `securitySolutionUI/rules/id/${mockDetailName}`, - }, - { - text: 'Edit', - href: '', - }, - ]); - }); - - it('should return null breadcrumbs when supplied Cases pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.case, '/', undefined), - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual(null); - }); - - it('should return null breadcrumbs when supplied Cases details pageName', () => { - const sampleCase = { - id: 'my-case-id', - name: 'Case name', - }; - const breadcrumbs = getBreadcrumbsForRoute( - { - ...getMockObject(SecurityPageName.case, `/${sampleCase.id}`, sampleCase.id), - state: { caseTitle: sampleCase.name }, - }, - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual(null); - }); - - it('should return Endpoints breadcrumbs when supplied endpoints pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.endpoints, '/', undefined), - getSecuritySolutionUrl - ); - - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - settingsBreadcrumb, - { - text: 'Endpoints', - href: '', - }, - ]); - }); - - it('should return Admin breadcrumbs when supplied admin pageName', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject(SecurityPageName.administration, '/', undefined), - getSecuritySolutionUrl - ); - - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - { - ...settingsBreadcrumb, - href: '', - }, - ]); - }); - it('should return Exceptions breadcrumbs when supplied exception Details pageName', () => { - const mockListName = 'new shared list'; - const breadcrumbs = getBreadcrumbsForRoute( - { - ...getMockObject( - SecurityPageName.exceptions, - `/exceptions/details/${mockListName}`, - undefined - ), - state: { - listName: mockListName, - }, - }, - getSecuritySolutionUrl - ); - expect(breadcrumbs).toEqual([ - securityBreadcrumb, - rulesLandingBreadcrumb, - exceptionsBreadcrumb, - { - text: mockListName, - href: ``, - }, - ]); - }); - }); - - describe('setBreadcrumbs()', () => { - it('should call chrome breadcrumb service with correct breadcrumbs', () => { - mockUseRouteSpy.mockReturnValueOnce([getMockObject(SecurityPageName.hosts, '/', hostName)]); - renderHook(useBreadcrumbs, { - initialProps: { isEnabled: true }, - wrapper: TestProviders, - }); - - expect(mockSetBreadcrumbs).toHaveBeenCalledWith([ - expect.objectContaining({ - text: 'Security', - href: 'securitySolutionUI/get_started', - onClick: expect.any(Function), - }), - expect.objectContaining({ - text: 'Explore', - href: 'securitySolutionUI/explore', - onClick: expect.any(Function), - }), - expect.objectContaining({ - text: 'Hosts', - href: 'securitySolutionUI/hosts', - onClick: expect.any(Function), - }), - expect.objectContaining({ - text: 'siem-kibana', - href: 'securitySolutionUI/hosts/name/siem-kibana', - onClick: expect.any(Function), - }), - { - text: 'Authentications', - href: '', - }, - ]); - }); - - it('should not call chrome breadcrumb service when not enabled', () => { - mockUseRouteSpy.mockReturnValueOnce([getMockObject(SecurityPageName.hosts, '/', hostName)]); - renderHook(useBreadcrumbs, { - initialProps: { isEnabled: false }, - wrapper: TestProviders, - }); - expect(mockSetBreadcrumbs).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts index cfbffae6ad8de..819b50bfb48be 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts @@ -4,136 +4,4 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { useEffect } from 'react'; -import { last } from 'lodash/fp'; -import { useDispatch } from 'react-redux'; -import type { ChromeBreadcrumb } from '@kbn/core/public'; -import { METRIC_TYPE } from '@kbn/analytics'; -import { getTrailingBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../explore/hosts/pages/details/utils'; -import { getTrailingBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../explore/network/pages/details/utils'; -import { getTrailingBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../detections/pages/detection_engine/rules/utils'; -import { getTrailingBreadcrumbs as geExceptionsBreadcrumbs } from '../../../../exceptions/utils/pages.utils'; -import { getTrailingBreadcrumbs as getCSPBreadcrumbs } from '../../../../cloud_security_posture/breadcrumbs'; -import { getTrailingBreadcrumbs as getUsersBreadcrumbs } from '../../../../explore/users/pages/details/utils'; -import { getTrailingBreadcrumbs as getKubernetesBreadcrumbs } from '../../../../kubernetes/pages/utils/breadcrumbs'; -import { getTrailingBreadcrumbs as getAlertDetailBreadcrumbs } from '../../../../detections/pages/alert_details/utils/breadcrumbs'; -import { getTrailingBreadcrumbs as getDashboardBreadcrumbs } from '../../../../dashboards/pages/utils'; -import { SecurityPageName } from '../../../../app/types'; -import type { RouteSpyState } from '../../../utils/route/types'; -import { timelineActions } from '../../../../timelines/store/timeline'; -import { TimelineId } from '../../../../../common/types/timeline'; -import { getLeadingBreadcrumbsForSecurityPage } from './get_breadcrumbs_for_page'; -import type { GetSecuritySolutionUrl } from '../../link_to'; -import { useGetSecuritySolutionUrl } from '../../link_to'; -import { TELEMETRY_EVENT, track } from '../../../lib/telemetry'; -import { useKibana } from '../../../lib/kibana'; -import { useRouteSpy } from '../../../utils/route/use_route_spy'; - -export const useBreadcrumbs = ({ isEnabled }: { isEnabled: boolean }) => { - const dispatch = useDispatch(); - const [routeProps] = useRouteSpy(); - const getSecuritySolutionUrl = useGetSecuritySolutionUrl(); - const { - chrome: { setBreadcrumbs }, - application: { navigateToUrl }, - } = useKibana().services; - - useEffect(() => { - if (!isEnabled) { - return; - } - const breadcrumbs = getBreadcrumbsForRoute(routeProps, getSecuritySolutionUrl); - if (!breadcrumbs) { - return; - } - setBreadcrumbs( - breadcrumbs.map((breadcrumb) => ({ - ...breadcrumb, - ...(breadcrumb.href && !breadcrumb.onClick - ? { - onClick: (ev) => { - ev.preventDefault(); - const trackedPath = breadcrumb.href?.split('?')[0] ?? 'unknown'; - track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.BREADCRUMB}${trackedPath}`); - dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - navigateToUrl(breadcrumb.href!); - }, - } - : {}), - })) - ); - }, [routeProps, isEnabled, dispatch, getSecuritySolutionUrl, setBreadcrumbs, navigateToUrl]); -}; - -export const getBreadcrumbsForRoute = ( - spyState: RouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] | null => { - if ( - !spyState?.pageName || - // cases manages its own breadcrumbs, return null - spyState.pageName === SecurityPageName.case - ) { - return null; - } - - const leadingBreadcrumbs = getLeadingBreadcrumbsForSecurityPage( - spyState.pageName, - getSecuritySolutionUrl - ); - - return emptyLastBreadcrumbUrl([ - ...leadingBreadcrumbs, - ...getTrailingBreadcrumbsForRoutes(spyState, getSecuritySolutionUrl), - ]); -}; - -const getTrailingBreadcrumbsForRoutes = ( - spyState: RouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { - switch (spyState.pageName) { - case SecurityPageName.hosts: - return getHostDetailsBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.network: - return getIPDetailsBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.users: - return getUsersBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.rules: - case SecurityPageName.rulesAdd: - case SecurityPageName.rulesCreate: - return getDetectionRulesBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.exceptions: - return geExceptionsBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.kubernetes: - return getKubernetesBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.alerts: - return getAlertDetailBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.cloudSecurityPostureBenchmarks: - return getCSPBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.dashboards: - return getDashboardBreadcrumbs(spyState); - } - - return []; -}; - -const emptyLastBreadcrumbUrl = (breadcrumbs: ChromeBreadcrumb[]) => { - const leadingBreadCrumbs = breadcrumbs.slice(0, -1); - const lastBreadcrumb = last(breadcrumbs); - - if (lastBreadcrumb) { - return [ - ...leadingBreadCrumbs, - { - ...lastBreadcrumb, - href: '', - }, - ]; - } - - return breadcrumbs; -}; +export { useBreadcrumbsNav } from './use_breadcrumbs_nav'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts new file mode 100644 index 0000000000000..5c45da1bb1ff2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.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 { SecurityPageName } from '../../../../../common'; +import type { GetTrailingBreadcrumbs } from './types'; + +import { getTrailingBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../explore/hosts/pages/details/breadcrumbs'; +import { getTrailingBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../explore/network/pages/details/breadcrumbs'; +import { getTrailingBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../detections/pages/detection_engine/rules/breadcrumbs'; +import { getTrailingBreadcrumbs as geExceptionsBreadcrumbs } from '../../../../exceptions/utils/breadcrumbs'; +import { getTrailingBreadcrumbs as getCSPBreadcrumbs } from '../../../../cloud_security_posture/breadcrumbs'; +import { getTrailingBreadcrumbs as getUsersBreadcrumbs } from '../../../../explore/users/pages/details/breadcrumbs'; +import { getTrailingBreadcrumbs as getKubernetesBreadcrumbs } from '../../../../kubernetes/pages/utils/breadcrumbs'; +import { getTrailingBreadcrumbs as getAlertDetailBreadcrumbs } from '../../../../detections/pages/alert_details/utils/breadcrumbs'; +import { getTrailingBreadcrumbs as getDashboardBreadcrumbs } from '../../../../dashboards/pages/breadcrumbs'; + +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( + spyState, + getSecuritySolutionUrl +) => { + switch (spyState.pageName) { + case SecurityPageName.hosts: + return getHostDetailsBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.network: + return getIPDetailsBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.users: + return getUsersBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.rules: + case SecurityPageName.rulesAdd: + case SecurityPageName.rulesCreate: + return getDetectionRulesBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.exceptions: + return geExceptionsBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.kubernetes: + return getKubernetesBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.alerts: + return getAlertDetailBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.cloudSecurityPostureBenchmarks: + return getCSPBreadcrumbs(spyState, getSecuritySolutionUrl); + case SecurityPageName.dashboards: + return getDashboardBreadcrumbs(spyState, getSecuritySolutionUrl); + } + return []; +}; diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/utils.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/types.ts similarity index 51% rename from x-pack/plugins/security_solution/public/dashboards/pages/utils.ts rename to x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/types.ts index c71eedbec0264..9efa8320b1fd7 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/types.ts @@ -6,13 +6,10 @@ */ import type { ChromeBreadcrumb } from '@kbn/core/public'; -import type { RouteSpyState } from '../../common/utils/route/types'; +import type { RouteSpyState } from '../../../utils/route/types'; +import type { GetSecuritySolutionUrl } from '../../link_to'; -export const getTrailingBreadcrumbs = (params: RouteSpyState): ChromeBreadcrumb[] => { - const breadcrumbName = params?.state?.dashboardName; - if (breadcrumbName) { - return [{ text: breadcrumbName }]; - } - - return []; -}; +export type GetTrailingBreadcrumbs = ( + spyState: T, + getSecuritySolutionUrl: GetSecuritySolutionUrl +) => ChromeBreadcrumb[]; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts new file mode 100644 index 0000000000000..25c29fdb9fa2d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import type { ChromeBreadcrumb } from '@kbn/core/public'; +import type { GetSecuritySolutionUrl } from '../../link_to'; +import { SecurityPageName } from '../../../../../common/constants'; +import type { LinkInfo, LinkItem } from '../../../links'; +import { useBreadcrumbsNav } from './use_breadcrumbs_nav'; +import type { BreadcrumbsNav } from '../../../breadcrumbs'; + +jest.mock('../../../lib/kibana'); + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: () => mockDispatch, +})); + +const link1Id = 'link-1' as SecurityPageName; +const link2Id = 'link-2' as SecurityPageName; +const link3Id = 'link-3' as SecurityPageName; +const link4Id = 'link-4' as SecurityPageName; +const link5Id = 'link-5' as SecurityPageName; + +const link1: LinkItem = { id: link1Id, title: 'link 1', path: '/link1' }; +const link2: LinkItem = { id: link2Id, title: 'link 2', path: '/link2' }; +const link3: LinkItem = { id: link3Id, title: 'link 3', path: '/link3' }; +const link4: LinkItem = { id: link4Id, title: 'link 4', path: '/link4' }; +const link5: LinkItem = { id: link5Id, title: 'link 5', path: '/link5' }; + +const ancestorsLinks = [link1, link2, link3]; +const trailingLinks = [link4, link5]; +const allLinks = [...ancestorsLinks, ...trailingLinks]; + +const mockSecuritySolutionUrl: GetSecuritySolutionUrl = jest.fn( + ({ deepLinkId }: { deepLinkId: SecurityPageName }) => + allLinks.find((link) => link.id === deepLinkId)?.path ?? deepLinkId +); +jest.mock('../../link_to', () => ({ + useGetSecuritySolutionUrl: () => mockSecuritySolutionUrl, +})); + +const mockUpdateBreadcrumbsNav = jest.fn((_param: BreadcrumbsNav) => {}); +jest.mock('../../../breadcrumbs', () => ({ + updateBreadcrumbsNav: (param: BreadcrumbsNav) => mockUpdateBreadcrumbsNav(param), +})); + +const mockUseRouteSpy = jest.fn((): [{ pageName: string }] => [{ pageName: link1Id }]); +jest.mock('../../../utils/route/use_route_spy', () => ({ + useRouteSpy: () => mockUseRouteSpy(), +})); + +const mockGetAncestorLinks = jest.fn((_id: unknown): LinkInfo[] => ancestorsLinks); +jest.mock('../../../links', () => ({ + ...jest.requireActual('../../../links'), + getAncestorLinksInfo: (id: unknown) => mockGetAncestorLinks(id), +})); + +const mockGetTrailingBreadcrumbs = jest.fn((): ChromeBreadcrumb[] => + trailingLinks.map(({ title: text, path: href }) => ({ text, href })) +); +jest.mock('./trailing_breadcrumbs', () => ({ + getTrailingBreadcrumbs: () => mockGetTrailingBreadcrumbs(), +})); + +const landingBreadcrumb = { + href: 'get_started', + text: 'Security', + onClick: expect.any(Function), +}; + +describe('useBreadcrumbsNav', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should process breadcrumbs with current pageName', () => { + renderHook(useBreadcrumbsNav); + expect(mockGetAncestorLinks).toHaveBeenCalledWith(link1Id); + expect(mockGetTrailingBreadcrumbs).toHaveBeenCalledWith(); + }); + + it('should not process breadcrumbs with empty pageName', () => { + mockUseRouteSpy.mockReturnValueOnce([{ pageName: '' }]); + renderHook(useBreadcrumbsNav); + expect(mockGetAncestorLinks).not.toHaveBeenCalled(); + expect(mockGetTrailingBreadcrumbs).not.toHaveBeenCalledWith(); + }); + + it('should not process breadcrumbs with cases pageName', () => { + mockUseRouteSpy.mockReturnValueOnce([{ pageName: SecurityPageName.case }]); + renderHook(useBreadcrumbsNav); + expect(mockGetAncestorLinks).not.toHaveBeenCalled(); + expect(mockGetTrailingBreadcrumbs).not.toHaveBeenCalledWith(); + }); + + it('should call updateBreadcrumbsNav with all breadcrumbs', () => { + renderHook(useBreadcrumbsNav); + expect(mockUpdateBreadcrumbsNav).toHaveBeenCalledWith({ + leading: [ + landingBreadcrumb, + { + href: link1.path, + text: link1.title, + onClick: expect.any(Function), + }, + { + href: link2.path, + text: link2.title, + onClick: expect.any(Function), + }, + { + href: link3.path, + text: link3.title, + onClick: expect.any(Function), + }, + ], + trailing: [ + { + href: link4.path, + text: link4.title, + onClick: expect.any(Function), + }, + { + href: link5.path, + text: link5.title, + onClick: expect.any(Function), + }, + ], + }); + }); + + it('should create breadcrumbs onClick handler', () => { + renderHook(useBreadcrumbsNav); + const event = { preventDefault: jest.fn() } as unknown as React.MouseEvent< + HTMLElement, + MouseEvent + >; + const breadcrumb = mockUpdateBreadcrumbsNav.mock.calls?.[0]?.[0]?.leading[1]; + breadcrumb?.onClick?.(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(mockDispatch).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts new file mode 100644 index 0000000000000..9eeae743bffaa --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import type { ChromeBreadcrumb } from '@kbn/core/public'; +import { METRIC_TYPE } from '@kbn/analytics'; +import type { Dispatch } from 'redux'; +import { SecurityPageName } from '../../../../app/types'; +import type { RouteSpyState } from '../../../utils/route/types'; +import { timelineActions } from '../../../../timelines/store/timeline'; +import { TimelineId } from '../../../../../common/types/timeline'; +import type { GetSecuritySolutionUrl } from '../../link_to'; +import { useGetSecuritySolutionUrl } from '../../link_to'; +import { TELEMETRY_EVENT, track } from '../../../lib/telemetry'; +import { useNavigateTo, type NavigateTo } from '../../../lib/kibana'; +import { useRouteSpy } from '../../../utils/route/use_route_spy'; +import { updateBreadcrumbsNav } from '../../../breadcrumbs'; +import { getAncestorLinksInfo } from '../../../links'; +import { APP_NAME } from '../../../../../common/constants'; +import { getTrailingBreadcrumbs } from './trailing_breadcrumbs'; + +export const useBreadcrumbsNav = () => { + const dispatch = useDispatch(); + const [routeProps] = useRouteSpy(); + const { navigateTo } = useNavigateTo(); + const getSecuritySolutionUrl = useGetSecuritySolutionUrl(); + + useEffect(() => { + // cases manages its own breadcrumbs + if (!routeProps.pageName || routeProps.pageName === SecurityPageName.case) { + return; + } + + const leadingBreadcrumbs = getLeadingBreadcrumbs(routeProps, getSecuritySolutionUrl); + const trailingBreadcrumbs = getTrailingBreadcrumbs(routeProps, getSecuritySolutionUrl); + + updateBreadcrumbsNav({ + leading: addOnClicksHandlers(leadingBreadcrumbs, dispatch, navigateTo), + trailing: addOnClicksHandlers(trailingBreadcrumbs, dispatch, navigateTo), + }); + }, [routeProps, getSecuritySolutionUrl, dispatch, navigateTo]); +}; + +const getLeadingBreadcrumbs = ( + { pageName }: RouteSpyState, + getSecuritySolutionUrl: GetSecuritySolutionUrl +): ChromeBreadcrumb[] => { + const landingBreadcrumb: ChromeBreadcrumb = { + text: APP_NAME, + href: getSecuritySolutionUrl({ deepLinkId: SecurityPageName.landing }), + }; + + const breadcrumbs: ChromeBreadcrumb[] = getAncestorLinksInfo(pageName).map(({ title, id }) => ({ + text: title, + href: getSecuritySolutionUrl({ deepLinkId: id }), + })); + + return [landingBreadcrumb, ...breadcrumbs]; +}; + +const addOnClicksHandlers = ( + breadcrumbs: ChromeBreadcrumb[], + dispatch: Dispatch, + navigateTo: NavigateTo +): ChromeBreadcrumb[] => + breadcrumbs.map((breadcrumb) => ({ + ...breadcrumb, + ...(breadcrumb.href && + !breadcrumb.onClick && { + onClick: createOnClickHandler(breadcrumb.href, dispatch, navigateTo), + }), + })); + +const createOnClickHandler = + (href: string, dispatch: Dispatch, navigateTo: NavigateTo): ChromeBreadcrumb['onClick'] => + (ev) => { + ev.preventDefault(); + const trackedPath = href.split('?')[0]; + track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.BREADCRUMB}${trackedPath}`); + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); + navigateTo({ url: href }); + }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx index 42f46525bdbe4..f1d94adc79a1b 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx @@ -9,9 +9,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { BehaviorSubject } from 'rxjs'; import { useSecuritySolutionNavigation } from './use_security_solution_navigation'; -const mockSetBreadcrumbs = jest.fn(); jest.mock('../breadcrumbs', () => ({ - useBreadcrumbs: () => mockSetBreadcrumbs, + useBreadcrumbsNav: () => jest.fn(), })); const mockIsSidebarEnabled$ = new BehaviorSubject(true); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx index bf4e8359cb464..315a73a950edf 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx @@ -16,7 +16,7 @@ import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import { useKibana } from '../../../lib/kibana'; -import { useBreadcrumbs } from '../breadcrumbs'; +import { useBreadcrumbsNav } from '../breadcrumbs'; import { SecuritySideNav } from '../security_side_nav'; const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', { @@ -27,9 +27,7 @@ export const useSecuritySolutionNavigation = (): KibanaPageTemplateProps['soluti const { isSidebarEnabled$ } = useKibana().services; const isSidebarEnabled = useObservable(isSidebarEnabled$); - useBreadcrumbs({ - isEnabled: true, // TODO: use isSidebarEnabled$ when serverless breadcrumb is ready - }); + useBreadcrumbsNav(); if (!isSidebarEnabled) { return undefined; diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_actions.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_actions.tsx index 4e4bd7cdf2d74..777b0d32306fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_actions.tsx @@ -37,7 +37,6 @@ interface OwnProps { totalItems: number; filterStatus?: AlertWorkflowStatus; query?: string; - indexName: string; showAlertStatusActions?: boolean; onActionSuccess?: OnUpdateAlertStatusSuccess; onActionFailure?: OnUpdateAlertStatusError; @@ -59,7 +58,6 @@ export const AlertBulkActionsComponent = React.memo) => { + return { + query: { + bool: { + filter: { + terms: { + _id: eventIds, + }, + }, + }, + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.test.ts b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.test.ts new file mode 100644 index 0000000000000..ccf45b091174d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { updateAlertStatus } from './update_alerts'; + +const mockUpdateAlertStatusByIds = jest.fn().mockReturnValue(new Promise(() => {})); +const mockUpdateAlertStatusByQuery = jest.fn().mockReturnValue(new Promise(() => {})); + +jest.mock('../../../../detections/containers/detection_engine/alerts/api', () => { + return { + updateAlertStatusByQuery: (params: unknown) => mockUpdateAlertStatusByQuery(params), + updateAlertStatusByIds: (params: unknown) => mockUpdateAlertStatusByIds(params), + }; +}); + +const status = 'open'; + +describe('updateAlertStatus', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should throw an error if neither query nor signalIds are provided', () => { + expect(() => { + updateAlertStatus({ status }); + }).toThrowError('Either query or signalIds must be provided'); + }); + + it('should call updateAlertStatusByIds if signalIds are provided', () => { + const signalIds = ['1', '2']; + updateAlertStatus({ + status, + signalIds, + }); + expect(mockUpdateAlertStatusByIds).toHaveBeenCalledWith({ + status, + signalIds, + }); + expect(mockUpdateAlertStatusByQuery).not.toHaveBeenCalled(); + }); + + it('should call mockUpdateAlertStatusByQuery if query is provided', () => { + const query = { query: 'query' }; + updateAlertStatus({ + status, + query, + }); + expect(mockUpdateAlertStatusByIds).not.toHaveBeenCalled(); + expect(mockUpdateAlertStatusByQuery).toHaveBeenCalledWith({ + status, + query, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.ts b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.ts new file mode 100644 index 0000000000000..2ffa195fee497 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/update_alerts.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UpdateByQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; +import { + updateAlertStatusByIds, + updateAlertStatusByQuery, +} from '../../../../detections/containers/detection_engine/alerts/api'; + +interface UpdatedAlertsResponse { + updated: number; + version_conflicts: UpdateByQueryResponse['version_conflicts']; +} + +interface UpdatedAlertsProps { + status: Status; + query?: object; + signalIds?: string[]; + signal?: AbortSignal; +} + +/** + * Update alert status by query or signalIds. + * Either query or signalIds must be provided + * `signalIds` is the preferred way to update alerts because it is more cost effective on Serverless. + * + * @param status to update to('open' / 'closed' / 'acknowledged') + * @param index index to be updated + * @param query optional query object to update alerts by query. + * @param signalIds optional signalIds to update alerts by signalIds. + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const updateAlertStatus = ({ + status, + query, + signalIds, + signal, +}: UpdatedAlertsProps): Promise => { + if (signalIds && signalIds.length > 0) { + return updateAlertStatusByIds({ status, signalIds, signal }).then(({ items }) => ({ + updated: items.length, + version_conflicts: 0, + })); + } else if (query) { + return updateAlertStatusByQuery({ status, query, signal }).then( + ({ updated, version_conflicts: conflicts }) => ({ + updated: updated ?? 0, + version_conflicts: conflicts, + }) + ); + } + throw new Error('Either query or signalIds must be provided'); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_action_items.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_action_items.tsx index d2647023539e8..34b6a523efa59 100644 --- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_action_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_action_items.tsx @@ -14,22 +14,17 @@ import type { SetEventsLoading, } from '../../../../../common/types'; import * as i18n from './translations'; -import { useUpdateAlertsStatus } from './use_update_alerts'; +import { updateAlertStatus } from './update_alerts'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import { useStartTransaction } from '../../../lib/apm/use_start_transaction'; import { APM_USER_INTERACTIONS } from '../../../lib/apm/constants'; import type { AlertWorkflowStatus } from '../../../types'; import type { OnUpdateAlertStatusError, OnUpdateAlertStatusSuccess } from './types'; -export const getUpdateAlertsQuery = (eventIds: Readonly) => { - return { bool: { filter: { terms: { _id: eventIds } } } }; -}; - export interface BulkActionsProps { eventIds: string[]; currentStatus?: AlertWorkflowStatus; query?: string; - indexName: string; setEventsLoading: SetEventsLoading; setEventsDeleted: SetEventsDeleted; showAlertStatusActions?: boolean; @@ -42,7 +37,6 @@ export const useBulkActionItems = ({ eventIds, currentStatus, query, - indexName, setEventsLoading, showAlertStatusActions = true, setEventsDeleted, @@ -50,7 +44,6 @@ export const useBulkActionItems = ({ onUpdateFailure, customBulkActions, }: BulkActionsProps) => { - const { updateAlertStatus } = useUpdateAlertsStatus(); const { addSuccess, addError, addWarning } = useAppToasts(); const { startTransaction } = useStartTransaction(); @@ -116,11 +109,10 @@ export const useBulkActionItems = ({ try { setEventsLoading({ eventIds, isLoading: true }); - const response = await updateAlertStatus({ - index: indexName, status, - query: query ? JSON.parse(query) : getUpdateAlertsQuery(eventIds), + query: query && JSON.parse(query), + signalIds: eventIds, }); // TODO: Only delete those that were successfully updated from updatedRules @@ -140,8 +132,6 @@ export const useBulkActionItems = ({ [ setEventsLoading, eventIds, - updateAlertStatus, - indexName, query, setEventsDeleted, onAlertStatusUpdateSuccess, diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_tags.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_tags.tsx index 4805c8dc996fb..9b211f9c259cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_tags.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_tags.tsx @@ -10,11 +10,11 @@ import type { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { getUpdateAlertsQuery } from '../../../../detections/components/alerts_table/actions'; import type { AlertTags } from '../../../../../common/detection_engine/schemas/common'; import { DETECTION_ENGINE_ALERT_TAGS_URL } from '../../../../../common/constants'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import * as i18n from './translations'; +import { getUpdateAlertsQuery } from './helpers'; export type SetAlertTagsFunc = ( tags: AlertTags, diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_update_alerts.ts b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_update_alerts.ts deleted file mode 100644 index 13ecf12b4479c..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_update_alerts.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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { CoreStart } from '@kbn/core/public'; - -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; -import type { AlertWorkflowStatus } from '../../../types'; - -/** - * Update alert status by query - * - * @param status to update to('open' / 'closed' / 'acknowledged') - * @param index index to be updated - * @param query optional query object to update alerts by query. - - * - * @throws An error if response is not OK - */ -export const useUpdateAlertsStatus = (): { - updateAlertStatus: (params: { - status: AlertWorkflowStatus; - index: string; - query: object; - }) => Promise; -} => { - const { http } = useKibana().services; - return { - updateAlertStatus: async ({ status, index, query }) => { - return http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { - method: 'POST', - body: JSON.stringify({ status, query }), - }); - }, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts index 659aa08d754bf..876f04393dcdc 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts @@ -70,6 +70,7 @@ export interface TopNOption { export const detectionAlertsTables: string[] = [ TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage, + TableId.alertsOnCasePage, TimelineId.casePage, ]; diff --git a/x-pack/plugins/security_solution/public/common/utils/lens.ts b/x-pack/plugins/security_solution/public/common/utils/lens.ts new file mode 100644 index 0000000000000..047144ce55f6f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/lens.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 { KBN_FIELD_TYPES } from '@kbn/field-types'; + +const SUPPORTED_LENS_TYPES = new Set([ + KBN_FIELD_TYPES.STRING, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.IP, +]); + +export const isLensSupportedType = (fieldType: string | undefined) => + fieldType ? SUPPORTED_LENS_TYPES.has(fieldType as KBN_FIELD_TYPES) : false; diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/breadcrumbs.ts b/x-pack/plugins/security_solution/public/dashboards/pages/breadcrumbs.ts new file mode 100644 index 0000000000000..01e663e8abb7e --- /dev/null +++ b/x-pack/plugins/security_solution/public/dashboards/pages/breadcrumbs.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 type { GetTrailingBreadcrumbs } from '../../common/components/navigation/breadcrumbs/types'; + +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = (params, getSecuritySolutionUrl) => { + const breadcrumbName = params?.state?.dashboardName; + if (breadcrumbName) { + return [{ text: breadcrumbName }]; + } + + return []; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx index a6dce444527d0..4c2493fbb81e4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx @@ -8,9 +8,6 @@ import { useEffect, useRef, useState } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; - -import { updateAlertStatus } from '../../../detections/containers/detection_engine/alerts/api'; -import { getUpdateAlertsQuery } from '../../../detections/components/alerts_table/actions'; import { buildAlertStatusesFilter, buildAlertsFilter, @@ -21,6 +18,7 @@ import { prepareExceptionItemsForBulkClose } from '../utils/helpers'; import * as i18nCommon from '../../../common/translations'; import * as i18n from './translations'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { updateAlertStatus } from '../../../common/components/toolbar/bulk_actions/update_alerts'; /** * Closes alerts. @@ -65,7 +63,7 @@ export const useCloseAlertsFromExceptions = (): ReturnUseCloseAlertsFromExceptio let bulkResponse: estypes.UpdateByQueryResponse | undefined; if (alertIdToClose != null) { alertIdResponse = await updateAlertStatus({ - query: getUpdateAlertsQuery([alertIdToClose]), + signalIds: [alertIdToClose], status: 'closed', signal: abortCtrl.signal, }); @@ -88,9 +86,7 @@ export const useCloseAlertsFromExceptions = (): ReturnUseCloseAlertsFromExceptio ); bulkResponse = await updateAlertStatus({ - query: { - query: filter, - }, + query: filter, status: 'closed', signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts index 2e6e0c622301e..965bb48a8c979 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts @@ -36,7 +36,7 @@ export const INSTALL_RULE_FAILED = (failed: number) => export const RULE_UPGRADE_FAILED = i18n.translate( 'xpack.securitySolution.detectionEngine.prebuiltRules.toast.ruleUpgradeFailed', { - defaultMessage: 'Rule upgrade failed', + defaultMessage: 'Rule update failed', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx index b9fd334e9393c..8e38be7b45db8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx @@ -27,7 +27,11 @@ export const AddPrebuiltRulesHeaderButtons = () => { {shouldDisplayInstallSelectedRulesButton ? ( - + {i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)} {isRuleInstalling ? : undefined} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx index 7c8b397f1badd..d956b2167bc8b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx @@ -11,10 +11,13 @@ import { EuiProgress, EuiSkeletonTitle, EuiSkeletonText, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import React from 'react'; import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants'; +import { RulesChangelogLink } from '../rules_changelog_link'; import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_items_message'; import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context'; import { AddPrebuiltRulesTableFilters } from './add_prebuilt_rules_table_filters'; @@ -67,7 +70,14 @@ export const AddPrebuiltRulesTable = React.memo(() => { ) : ( <> - + + + + + + + + { + const { docLinks } = useKibana().services; + + return ( + + {i18n.RULE_UPDATES_DOCUMENTATION_LINK} + + ); +}); + +RulesChangelogLink.displayName = 'RulesChangelogLink'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx index b7848eb5379e7..2c1eee1bf08b2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx @@ -18,6 +18,7 @@ import { import React from 'react'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants'; +import { RulesChangelogLink } from '../rules_changelog_link'; import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons'; import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; import { UpgradePrebuiltRulesTableFilters } from './upgrade_prebuilt_rules_table_filters'; @@ -78,12 +79,24 @@ export const UpgradePrebuiltRulesTable = React.memo(() => { NO_ITEMS_MESSAGE ) : ( <> - - - + + + - + + + + + + + + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx index 5dfff6c5fc462..b0d305dd0cf69 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx @@ -27,7 +27,11 @@ export const UpgradePrebuiltRulesTableButtons = () => { {shouldDisplayUpgradeSelectedRulesButton ? ( - + <> {i18n.UPDATE_SELECTED_RULES(numberOfSelectedRules)} {isRuleUpgrading ? : undefined} @@ -41,6 +45,7 @@ export const UpgradePrebuiltRulesTableButtons = () => { iconType="plusInCircle" onClick={upgradeAllRules} disabled={!isRulesAvailableForUpgrade || isRequestInProgress} + data-test-subj="upgradeAllRulesButton" > {i18n.UPDATE_ALL} {isRuleUpgrading ? : undefined} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx index ba68f2ef3eb36..43c09c7bf451c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx @@ -20,9 +20,9 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout'; import { MlJobCompatibilityCallout } from '../../../../detections/components/callouts/ml_job_compatibility_callout'; import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout'; -import { LoadPrePackagedRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button'; -import { useUserData } from '../../../../detections/components/user_info'; +import { AddElasticRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/add_elastic_rules_button'; import { ValueListsFlyout } from '../../../../detections/components/value_lists_management_flyout'; +import { useUserData } from '../../../../detections/components/user_info'; import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; import { redirectToDetections } from '../../../../detections/pages/detection_engine/rules/helpers'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; @@ -106,7 +106,7 @@ const RulesPageComponent: React.FC = () => { - + diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts index 2658a0af00d6b..86c8719053c81 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts @@ -14,6 +14,7 @@ import type { BrowserField } from '@kbn/timelines-plugin/common'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; import { getScopeFromPath, useSourcererDataView } from '../../../../common/containers/sourcerer'; import { getAllFieldsByName } from '../../../../common/containers/source'; +import { isLensSupportedType } from '../../../../common/utils/lens'; export interface UseInspectButtonParams extends Pick { response: string; @@ -65,11 +66,6 @@ export function isDataViewFieldSubtypeNested(field: Partial) { return !!subTypeNested?.nested?.path; } -export function isLensSupportedType(fieldType: string | undefined) { - const supportedTypes = new Set(['string', 'boolean', 'number', 'ip']); - return fieldType ? supportedTypes.has(fieldType) : false; -} - export interface GetAggregatableFields { [fieldName: string]: Partial; } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 8e8973310056e..28d46d96953b8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -44,7 +44,6 @@ import { import type { TimelineResult } from '../../../../common/types/timeline/api'; import { TimelineId } from '../../../../common/types/timeline'; import { TimelineStatus, TimelineType } from '../../../../common/types/timeline/api'; -import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; import type { SendAlertToTimelineActionProps, ThresholdAggregationData, @@ -83,20 +82,7 @@ import { DEFAULT_FROM_MOMENT, DEFAULT_TO_MOMENT, } from '../../../common/utils/default_date_settings'; - -export const getUpdateAlertsQuery = (eventIds: Readonly) => { - return { - query: { - bool: { - filter: { - terms: { - _id: eventIds, - }, - }, - }, - }, - }; -}; +import { updateAlertStatus } from '../../../common/components/toolbar/bulk_actions/update_alerts'; export const updateAlertStatusAction = async ({ query, @@ -110,8 +96,12 @@ export const updateAlertStatusAction = async ({ try { setEventsLoading({ eventIds: alertIds, isLoading: true }); - const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - const response = await updateAlertStatus({ query: queryObject, status: selectedStatus }); + const response = await updateAlertStatus({ + query: query && JSON.parse(query), + status: selectedStatus, + signalIds: alertIds, + }); + // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx index f2fa9e8bb6d1b..2465de8f14c26 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx @@ -235,7 +235,6 @@ export const GroupedSubLevelComponent: React.FC = ({ ); const takeActionItems = useGroupTakeActionsItems({ - indexName: indexPattern.title, currentStatus: currentAlertStatusFilterValue, showAlertStatusActions: hasIndexWrite && hasIndexMaintenance, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx index d663b2abc2c61..6470c9a4abc66 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx @@ -35,7 +35,6 @@ describe('useGroupTakeActionsItems', () => { const { result, waitForNextUpdate } = renderHook( () => useGroupTakeActionsItems({ - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -53,7 +52,6 @@ describe('useGroupTakeActionsItems', () => { () => useGroupTakeActionsItems({ currentStatus: [], - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -71,7 +69,6 @@ describe('useGroupTakeActionsItems', () => { () => useGroupTakeActionsItems({ currentStatus: ['open', 'closed'], - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -89,7 +86,6 @@ describe('useGroupTakeActionsItems', () => { () => useGroupTakeActionsItems({ currentStatus: ['open'], - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -110,7 +106,6 @@ describe('useGroupTakeActionsItems', () => { () => useGroupTakeActionsItems({ currentStatus: ['closed'], - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -131,7 +126,6 @@ describe('useGroupTakeActionsItems', () => { () => useGroupTakeActionsItems({ currentStatus: ['acknowledged'], - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { @@ -151,7 +145,6 @@ describe('useGroupTakeActionsItems', () => { const { result, waitForNextUpdate } = renderHook( () => useGroupTakeActionsItems({ - indexName: '.alerts-security.alerts-default', showAlertStatusActions: false, }), { @@ -167,7 +160,6 @@ describe('useGroupTakeActionsItems', () => { const { result, waitForNextUpdate } = renderHook( () => useGroupTakeActionsItems({ - indexName: '.alerts-security.alerts-default', showAlertStatusActions: true, }), { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx index 5d151d2e4cc88..1384c592162db 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx @@ -15,7 +15,7 @@ import { useStartTransaction } from '../../../../common/lib/apm/use_start_transa import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { AlertWorkflowStatus } from '../../../../common/types'; import { APM_USER_INTERACTIONS } from '../../../../common/lib/apm/constants'; -import { useUpdateAlertsStatus } from '../../../../common/components/toolbar/bulk_actions/use_update_alerts'; +import { updateAlertStatus } from '../../../../common/components/toolbar/bulk_actions/update_alerts'; import { BULK_ACTION_ACKNOWLEDGED_SELECTED, BULK_ACTION_CLOSE_SELECTED, @@ -33,16 +33,13 @@ import type { StartServices } from '../../../../types'; export interface TakeActionsProps { currentStatus?: Status[]; - indexName: string; showAlertStatusActions?: boolean; } export const useGroupTakeActionsItems = ({ currentStatus, - indexName, showAlertStatusActions = true, }: TakeActionsProps) => { - const { updateAlertStatus } = useUpdateAlertsStatus(); const { addSuccess, addError, addWarning } = useAppToasts(); const { startTransaction } = useStartTransaction(); const getGlobalQuerySelector = inputsSelectors.globalQuery(); @@ -163,7 +160,6 @@ export const useGroupTakeActionsItems = ({ try { const response = await updateAlertStatus({ - index: indexName, status, query: query ? JSON.parse(query) : {}, }); @@ -176,8 +172,6 @@ export const useGroupTakeActionsItems = ({ [ startTransaction, reportAlertsGroupingTakeActionClick, - updateAlertStatus, - indexName, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 57b0491446121..a9e3958332322 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -177,7 +177,6 @@ const AlertContextMenuComponent: React.FC void; eventId: string; scopeId: string; - indexName: string; refetch?: () => void; } @@ -28,7 +27,6 @@ export const useAlertsActions = ({ closePopover, eventId, scopeId, - indexName, refetch, }: Props) => { const dispatch = useDispatch(); @@ -63,7 +61,6 @@ export const useAlertsActions = ({ const actionItems = useBulkActionItems({ eventIds: [eventId], currentStatus: alertStatus as AlertWorkflowStatus, - indexName, setEventsLoading: localSetEventsLoading, setEventsDeleted, onUpdateSuccess: onStatusUpdate, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx similarity index 90% rename from x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx rename to x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx index 76e3a6e355667..014ba9d2c53e8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx @@ -14,17 +14,17 @@ import { useGetSecuritySolutionLinkProps } from '../../../../common/components/l import { SecurityPageName } from '../../../../../common'; import { usePrebuiltRulesStatus } from '../../../../detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_status'; -interface LoadPrePackagedRulesButtonProps { +interface AddElasticRulesButtonProps { 'data-test-subj'?: string; fill?: boolean; showBadge?: boolean; } -export const LoadPrePackagedRulesButton = ({ - 'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn', +export const AddElasticRulesButton = ({ + 'data-test-subj': dataTestSubj = 'addElasticRulesButton', fill, showBadge = true, -}: LoadPrePackagedRulesButtonProps) => { +}: AddElasticRulesButtonProps) => { const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); const { onClick: onClickLink } = getSecuritySolutionLinkProps({ deepLinkId: SecurityPageName.rulesAdd, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index da6ccd39753c8..803aa86b8d88d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -8,7 +8,7 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { memo } from 'react'; import styled from 'styled-components'; -import { LoadPrePackagedRulesButton } from './load_prepackaged_rules_button'; +import { AddElasticRulesButton } from './add_elastic_rules_button'; import * as i18n from './translations'; const EmptyPrompt = styled(EuiEmptyPrompt)` @@ -26,9 +26,9 @@ const PrePackagedRulesPromptComponent = () => { actions={ - diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx index 1a9c71b68bc4f..5874abe8397c5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx @@ -99,7 +99,6 @@ describe('take action dropdown', () => { detailsData: generateAlertDetailsDataMock() as TimelineEventsDetailsItem[], ecsData: getDetectionAlertMock(), handleOnEventClosed: jest.fn(), - indexName: 'index', isHostIsolationPanelOpen: false, loadingEventDetails: false, onAddEventFilterClick: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index 666db98405454..107af049ae6af 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -48,7 +48,6 @@ export interface TakeActionDropdownProps { detailsData: TimelineEventsDetailsItem[] | null; ecsData?: Ecs; handleOnEventClosed: () => void; - indexName: string; isHostIsolationPanelOpen: boolean; loadingEventDetails: boolean; onAddEventFilterClick: () => void; @@ -65,7 +64,6 @@ export const TakeActionDropdown = React.memo( detailsData, ecsData, handleOnEventClosed, - indexName, isHostIsolationPanelOpen, loadingEventDetails, onAddEventFilterClick, @@ -180,7 +178,6 @@ export const TakeActionDropdown = React.memo( alertStatus: actionsData.alertStatus, closePopover: closePopoverAndFlyout, eventId: actionsData.eventId, - indexName, refetch, scopeId, }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index eea9500e868a8..601bbf9498617 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -16,11 +16,12 @@ import { } from './mock'; import { fetchQueryAlerts, - updateAlertStatus, getSignalIndex, getUserPrivilege, createSignalIndex, createHostIsolation, + updateAlertStatusByQuery, + updateAlertStatusByIds, } from './api'; import { coreMock } from '@kbn/core/public/mocks'; @@ -57,40 +58,40 @@ describe('Detections Alerts API', () => { }); }); - describe('updateAlertStatus', () => { + describe('updateAlertStatusByQuery', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue({}); }); test('check parameter url, body when closing an alert', async () => { - await updateAlertStatus({ + await updateAlertStatusByQuery({ query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'closed', }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"conflicts":"proceed","status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + body: '{"conflicts":"proceed","status":"closed","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', method: 'POST', signal: abortCtrl.signal, }); }); test('check parameter url, body when opening an alert', async () => { - await updateAlertStatus({ + await updateAlertStatusByQuery({ query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"conflicts":"proceed","status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + body: '{"conflicts":"proceed","status":"open","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', method: 'POST', signal: abortCtrl.signal, }); }); test('happy path', async () => { - const alertsResp = await updateAlertStatus({ + const alertsResp = await updateAlertStatusByQuery({ query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', @@ -99,6 +100,48 @@ describe('Detections Alerts API', () => { }); }); + describe('updateAlertStatusById', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue({}); + }); + + test('check parameter url, body when closing an alert', async () => { + await updateAlertStatusByIds({ + signalIds: ['123'], + signal: abortCtrl.signal, + status: 'closed', + }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { + body: '{"status":"closed","signal_ids":["123"]}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('check parameter url, body when opening an alert', async () => { + await updateAlertStatusByIds({ + signalIds: ['123'], + signal: abortCtrl.signal, + status: 'open', + }); + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { + body: '{"status":"open","signal_ids":["123"]}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const alertsResp = await updateAlertStatusByIds({ + signalIds: ['123'], + signal: abortCtrl.signal, + status: 'open', + }); + expect(alertsResp).toEqual({}); + }); + }); + describe('getSignalIndex', () => { beforeEach(() => { fetchMock.mockClear(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index d8d1bae460a6d..91430d4818317 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -24,9 +24,10 @@ import type { QueryAlerts, AlertSearchResponse, AlertsIndex, - UpdateAlertStatusProps, + UpdateAlertStatusByQueryProps, CasesFromAlertsResponse, CheckSignalIndex, + UpdateAlertStatusByIdsProps, } from './types'; import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isolation'; import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables'; @@ -84,14 +85,34 @@ export const fetchQueryRuleRegistryAlerts = async ({ * * @throws An error if response is not OK */ -export const updateAlertStatus = async ({ +export const updateAlertStatusByQuery = async ({ query, status, signal, -}: UpdateAlertStatusProps): Promise => +}: UpdateAlertStatusByQueryProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', - body: JSON.stringify({ conflicts: 'proceed', status, ...query }), + body: JSON.stringify({ conflicts: 'proceed', status, query }), + signal, + }); + +/** + * Update alert status by signalIds + * + * @param signalIds List of signal ids to update + * @param status to update to('open' / 'closed' / 'acknowledged') + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const updateAlertStatusByIds = async ({ + signalIds, + status, + signal, +}: UpdateAlertStatusByIdsProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { + method: 'POST', + body: JSON.stringify({ status, signal_ids: signalIds }), signal, }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts index 9d39a3f39770f..717f2bee7a768 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts @@ -38,10 +38,16 @@ export interface AlertSearchResponse }; } -export interface UpdateAlertStatusProps { +export interface UpdateAlertStatusByQueryProps { query: object; status: Status; - signal?: AbortSignal; // TODO: implement cancelling + signal?: AbortSignal; +} + +export interface UpdateAlertStatusByIdsProps { + signalIds: string[]; + status: Status; + signal?: AbortSignal; } export interface AlertsIndex { diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx index 0f214d2f9b959..5ac7628f5376f 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx @@ -12,14 +12,12 @@ import { buildEsQuery } from '@kbn/es-query'; import type { TableId } from '@kbn/securitysolution-data-table'; import type { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { APM_USER_INTERACTIONS } from '../../../common/lib/apm/constants'; -import { useUpdateAlertsStatus } from '../../../common/components/toolbar/bulk_actions/use_update_alerts'; -import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { updateAlertStatus } from '../../../common/components/toolbar/bulk_actions/update_alerts'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction'; import type { AlertWorkflowStatus } from '../../../common/types'; import { FILTER_CLOSED, FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types'; import * as i18n from '../translations'; -import { getUpdateAlertsQuery } from '../../components/alerts_table/actions'; import { buildTimeRangeFilter } from '../../components/alerts_table/helpers'; interface UseBulkAlertActionItemsArgs { @@ -45,7 +43,6 @@ export const useBulkAlertActionItems = ({ }: UseBulkAlertActionItemsArgs) => { const { startTransaction } = useStartTransaction(); - const { updateAlertStatus } = useUpdateAlertsStatus(); const { addSuccess, addError, addWarning } = useAppToasts(); const onAlertStatusUpdateSuccess = useCallback( @@ -92,8 +89,6 @@ export const useBulkAlertActionItems = ({ [addError] ); - const { selectedPatterns } = useSourcererDataView(scopeId); - const getOnAction = useCallback( (status: AlertWorkflowStatus) => { const onActionClick: BulkActionsConfig['onClick'] = async ( @@ -103,14 +98,13 @@ export const useBulkAlertActionItems = ({ clearSelection, refresh ) => { - const ids = items.map((item) => item._id); - let query: Record = getUpdateAlertsQuery(ids).query; + let ids: string[] | undefined = items.map((item) => item._id); + let query: Record | undefined; if (isSelectAllChecked) { const timeFilter = buildTimeRangeFilter(from, to); query = buildEsQuery(undefined, [], [...timeFilter, ...filters], undefined); - } - if (query) { + ids = undefined; startTransaction({ name: APM_USER_INTERACTIONS.BULK_QUERY_STATUS_UPDATE }); } else if (items.length > 1) { startTransaction({ name: APM_USER_INTERACTIONS.BULK_STATUS_UPDATE }); @@ -121,9 +115,9 @@ export const useBulkAlertActionItems = ({ try { setAlertLoading(true); const response = await updateAlertStatus({ - index: selectedPatterns.join(','), status, query, + signalIds: ids, }); setAlertLoading(false); @@ -150,8 +144,6 @@ export const useBulkAlertActionItems = ({ [ onAlertStatusUpdateFailure, onAlertStatusUpdateSuccess, - updateAlertStatus, - selectedPatterns, startTransaction, filters, from, diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx index b6987f2fe29f6..e945e2c4828f2 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx @@ -8,12 +8,11 @@ import type { BrowserField, TimelineNonEcsData } from '@kbn/timelines-plugin/common'; import type { AlertsTableConfigurationRegistry } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useCallback, useMemo } from 'react'; -import { tableDefaults, dataTableSelectors } from '@kbn/securitysolution-data-table'; -import type { TableId } from '@kbn/securitysolution-data-table'; +import { TableId, tableDefaults, dataTableSelectors } from '@kbn/securitysolution-data-table'; import { getAllFieldsByName } from '../../../common/containers/source'; import type { UseDataGridColumnsSecurityCellActionsProps } from '../../../common/components/cell_actions'; import { useDataGridColumnsSecurityCellActions } from '../../../common/components/cell_actions'; -import { SecurityCellActionsTrigger } from '../../../actions/constants'; +import { SecurityCellActionsTrigger, SecurityCellActionType } from '../../../actions/constants'; import { VIEW_SELECTION } from '../../../../common/constants'; import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -89,12 +88,16 @@ export const getUseCellActionsHook = (tableId: TableId) => { [finalData] ); + const disabledActionTypes = + tableId === TableId.alertsOnCasePage ? [SecurityCellActionType.FILTER] : undefined; + const cellActions = useDataGridColumnsSecurityCellActions({ triggerId: SecurityCellActionsTrigger.DEFAULT, fields: cellActionsFields, getCellValue, metadata: cellActionsMetadata, dataGridRef, + disabledActionTypes, }); const getCellActions = useCallback( diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts index 632c40816476b..2b6dca72bf078 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts @@ -6,7 +6,7 @@ */ import type { ChromeBreadcrumb } from '@kbn/core/public'; -import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; +import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; import { getAlertDetailsUrl } from '../../../../common/components/link_to'; import { SecurityPageName } from '../../../../../common/constants'; import type { AlertDetailRouteSpyState } from '../../../../common/utils/route/types'; @@ -17,10 +17,15 @@ const TabNameMappedToI18nKey: Record = { [AlertDetailRouteType.summary]: i18n.SUMMARY_PAGE_TITLE, }; -export const getTrailingBreadcrumbs = ( - params: AlertDetailRouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( + params, + getSecuritySolutionUrl +) => { let breadcrumb: ChromeBreadcrumb[] = []; if (params.detailName != null) { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/breadcrumbs.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/breadcrumbs.ts new file mode 100644 index 0000000000000..0bd84ee4724ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/breadcrumbs.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ChromeBreadcrumb } from '@kbn/core/public'; +import { + getRuleDetailsTabUrl, + getRuleDetailsUrl, +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import * as i18nRules from './translations'; +import { SecurityPageName } from '../../../../app/types'; +import { RULES_PATH } from '../../../../../common/constants'; +import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; +import { + RuleDetailTabs, + RULE_DETAILS_TAB_NAME, +} from '../../../../detection_engine/rule_details_ui/pages/rule_details'; +import { DELETED_RULE } from '../../../../detection_engine/rule_details_ui/pages/rule_details/translations'; + +const getRuleDetailsTabName = (tabName: string): string => { + return RULE_DETAILS_TAB_NAME[tabName] ?? RULE_DETAILS_TAB_NAME[RuleDetailTabs.alerts]; +}; + +const isRuleCreatePage = (pathname: string) => + pathname.includes(RULES_PATH) && pathname.includes('/create'); + +const isRuleEditPage = (pathname: string) => + pathname.includes(RULES_PATH) && pathname.includes('/edit'); + +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = (params, getSecuritySolutionUrl) => { + let breadcrumb: ChromeBreadcrumb[] = []; + + if (params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.ruleName, + href: getSecuritySolutionUrl({ + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(params.detailName, ''), + }), + }, + ]; + } + + if (params.detailName && params.state?.ruleName && params.tabName) { + breadcrumb = [ + ...breadcrumb, + { + text: getRuleDetailsTabName(params.tabName), + href: getSecuritySolutionUrl({ + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsTabUrl(params.detailName, params.tabName, ''), + }), + }, + ]; + } + + if (isRuleCreatePage(params.pathName)) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.ADD_PAGE_TITLE, + href: '', + }, + ]; + } + + if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.EDIT_PAGE_TITLE, + href: '', + }, + ]; + } + + if (!isRuleEditPage(params.pathName) && params.state && !params.state.isExistingRule) { + breadcrumb = [...breadcrumb, { text: DELETED_RULE, href: '' }]; + } + + return breadcrumb; +}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 9bce60e23ae79..cbe9390b510ee 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -1218,3 +1218,10 @@ export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate( defaultMessage: 'Go back to installed Elastic rules', } ); + +export const RULE_UPDATES_DOCUMENTATION_LINK = i18n.translate( + 'xpack.securitySolution.ruleUpdates.documentationLink', + { + defaultMessage: "See what's new in Prebuilt Security Detection Rules", + } +); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 3f8f6315f3471..805ac2d37741a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -5,27 +5,13 @@ * 2.0. */ -import type { ChromeBreadcrumb } from '@kbn/core/public'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; -import { - getRuleDetailsTabUrl, - getRuleDetailsUrl, -} from '../../../../common/components/link_to/redirect_to_detection_engine'; -import * as i18nRules from './translations'; -import type { RouteSpyState } from '../../../../common/utils/route/types'; -import { SecurityPageName } from '../../../../app/types'; -import { DEFAULT_THREAT_MATCH_QUERY, RULES_PATH } from '../../../../../common/constants'; +import { DEFAULT_THREAT_MATCH_QUERY } from '../../../../../common/constants'; import type { AboutStepRule, DefineStepRule, RuleStepsOrder, ScheduleStepRule } from './types'; import { DataSourceType, GroupByOptions, RuleStep } from './types'; -import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/rule_schema'; -import { - RuleDetailTabs, - RULE_DETAILS_TAB_NAME, -} from '../../../../detection_engine/rule_details_ui/pages/rule_details'; -import { DELETED_RULE } from '../../../../detection_engine/rule_details_ui/pages/rule_details/translations'; import { fillEmptySeverityMappings } from './helpers'; export const ruleStepsOrder: RuleStepsOrder = [ @@ -35,75 +21,6 @@ export const ruleStepsOrder: RuleStepsOrder = [ RuleStep.ruleActions, ]; -const getRuleDetailsTabName = (tabName: string): string => { - return RULE_DETAILS_TAB_NAME[tabName] ?? RULE_DETAILS_TAB_NAME[RuleDetailTabs.alerts]; -}; - -const isRuleCreatePage = (pathname: string) => - pathname.includes(RULES_PATH) && pathname.includes('/create'); - -const isRuleEditPage = (pathname: string) => - pathname.includes(RULES_PATH) && pathname.includes('/edit'); - -export const getTrailingBreadcrumbs = ( - params: RouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { - let breadcrumb: ChromeBreadcrumb[] = []; - - if (params.detailName && params.state?.ruleName) { - breadcrumb = [ - ...breadcrumb, - { - text: params.state.ruleName, - href: getSecuritySolutionUrl({ - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(params.detailName, ''), - }), - }, - ]; - } - - if (params.detailName && params.state?.ruleName && params.tabName) { - breadcrumb = [ - ...breadcrumb, - { - text: getRuleDetailsTabName(params.tabName), - href: getSecuritySolutionUrl({ - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsTabUrl(params.detailName, params.tabName, ''), - }), - }, - ]; - } - - if (isRuleCreatePage(params.pathName)) { - breadcrumb = [ - ...breadcrumb, - { - text: i18nRules.ADD_PAGE_TITLE, - href: '', - }, - ]; - } - - if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { - breadcrumb = [ - ...breadcrumb, - { - text: i18nRules.EDIT_PAGE_TITLE, - href: '', - }, - ]; - } - - if (!isRuleEditPage(params.pathName) && params.state && !params.state.isExistingRule) { - breadcrumb = [...breadcrumb, { text: DELETED_RULE, href: '' }]; - } - - return breadcrumb; -}; - export const threatDefault = [ { framework: 'MITRE ATT&CK', diff --git a/x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts b/x-pack/plugins/security_solution/public/exceptions/utils/breadcrumbs.ts similarity index 61% rename from x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts rename to x-pack/plugins/security_solution/public/exceptions/utils/breadcrumbs.ts index 9c1a3289aca6f..a4f37ef18feec 100644 --- a/x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts +++ b/x-pack/plugins/security_solution/public/exceptions/utils/breadcrumbs.ts @@ -6,16 +6,17 @@ */ import type { ChromeBreadcrumb } from '@kbn/core/public'; import { EXCEPTIONS_PATH } from '../../../common/constants'; -import type { GetSecuritySolutionUrl } from '../../common/components/link_to'; -import type { RouteSpyState } from '../../common/utils/route/types'; +import type { GetTrailingBreadcrumbs } from '../../common/components/navigation/breadcrumbs/types'; const isListDetailPage = (pathname: string) => pathname.includes(EXCEPTIONS_PATH) && pathname.includes('/details'); -export const getTrailingBreadcrumbs = ( - params: RouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = (params, getSecuritySolutionUrl) => { let breadcrumb: ChromeBreadcrumb[] = []; if (isListDetailPage(params.pathName) && params.state?.listName) { diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/utils.ts b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/breadcrumbs.ts similarity index 78% rename from x-pack/plugins/security_solution/public/explore/hosts/pages/details/utils.ts rename to x-pack/plugins/security_solution/public/explore/hosts/pages/details/breadcrumbs.ts index 634f4e3889cb6..4b41229c4a1a0 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/breadcrumbs.ts @@ -8,16 +8,13 @@ import { get } from 'lodash/fp'; import type { ChromeBreadcrumb } from '@kbn/core/public'; -import { hostsModel } from '../../store'; import { HostsTableType } from '../../store/model'; import { getHostDetailsUrl } from '../../../../common/components/link_to/redirect_to_hosts'; import * as i18n from '../translations'; import type { HostRouteSpyState } from '../../../../common/utils/route/types'; import { SecurityPageName } from '../../../../app/types'; -import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; - -export const type = hostsModel.HostsType.details; +import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; const TabNameMappedToI18nKey: Record = { [HostsTableType.hosts]: i18n.NAVIGATION_ALL_HOSTS_TITLE, @@ -29,10 +26,15 @@ const TabNameMappedToI18nKey: Record = { [HostsTableType.sessions]: i18n.NAVIGATION_SESSIONS_TITLE, }; -export const getTrailingBreadcrumbs = ( - params: HostRouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( + params, + getSecuritySolutionUrl +) => { let breadcrumb: ChromeBreadcrumb[] = []; if (params.detailName != null) { diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.test.tsx index 611c1fb95a995..df38f06888539 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.test.tsx @@ -20,10 +20,9 @@ import { } from '../../../../common/mock'; import { HostDetailsTabs } from './details_tabs'; import { hostDetailsPagePath } from '../types'; -import { type } from './utils'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { getHostDetailsPageFilters } from './helpers'; -import { HostsTableType } from '../../store/model'; +import { HostsType, HostsTableType } from '../../store/model'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; import type { State } from '../../../../common/store'; import { createStore } from '../../../../common/store'; @@ -123,7 +122,7 @@ describe('body', () => { hostDetailsPagePath={hostDetailsPagePath} indexNames={[]} indexPattern={mockIndexPattern} - type={type} + type={HostsType.details} hostDetailsFilter={mockHostDetailsPageFilters} filterQuery={filterQuery} from={'2020-07-07T08:20:18.966Z'} diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.tsx index 13955a6e6b013..cc21c96ac9405 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/details_tabs.tsx @@ -10,14 +10,13 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { TableId } from '@kbn/securitysolution-data-table'; import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { RiskDetailsTabBody } from '../../../components/risk_score/risk_details_tab_body'; -import { HostsTableType } from '../../store/model'; +import { HostsType, HostsTableType } from '../../store/model'; import { AnomaliesQueryTabBody } from '../../../../common/containers/anomalies/anomalies_query_tab_body'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { AnomaliesHostTable } from '../../../../common/components/ml/tables/anomalies_host_table'; import { EventsQueryTabBody } from '../../../../common/components/events_tab'; import type { HostDetailsTabsProps } from './types'; -import { type } from './utils'; import { AuthenticationsQueryTabBody, @@ -43,7 +42,7 @@ export const HostDetailsTabs = React.memo( skip: isInitializing || filterQuery === undefined, setQuery, startDate: from, - type, + type: HostsType.details, indexPattern, indexNames, hostName: detailName, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx index 107cdb8af4de6..5d0ff73569bdc 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx @@ -49,7 +49,7 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { HostDetailsTabs } from './details_tabs'; import { navTabsHostDetails } from './nav_tabs'; import type { HostDetailsProps } from './types'; -import { type } from './utils'; +import { HostsType } from '../../store/model'; import { getHostDetailsPageFilters } from './helpers'; import { showGlobalFilters } from '../../../../timelines/components/timeline/helpers'; import { useGlobalFullScreen } from '../../../../common/containers/use_full_screen'; @@ -269,7 +269,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta to={to} from={from} detailName={detailName} - type={type} + type={HostsType.details} setQuery={setQuery} filterQuery={stringifiedAdditionalFilters} hostDetailsPagePath={hostDetailsPagePath} diff --git a/x-pack/plugins/security_solution/public/explore/network/pages/details/utils.ts b/x-pack/plugins/security_solution/public/explore/network/pages/details/breadcrumbs.ts similarity index 79% rename from x-pack/plugins/security_solution/public/explore/network/pages/details/utils.ts rename to x-pack/plugins/security_solution/public/explore/network/pages/details/breadcrumbs.ts index ba8bb5ec7acd4..d3aaa9fba6af7 100644 --- a/x-pack/plugins/security_solution/public/explore/network/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/explore/network/pages/details/breadcrumbs.ts @@ -10,15 +10,13 @@ import { get } from 'lodash/fp'; import type { ChromeBreadcrumb } from '@kbn/core/public'; import { decodeIpv6 } from '../../../../common/lib/helpers'; import { getNetworkDetailsUrl } from '../../../../common/components/link_to/redirect_to_network'; -import { networkModel } from '../../store'; import * as i18n from '../translations'; import { NetworkDetailsRouteType } from './types'; import type { NetworkRouteSpyState } from '../../../../common/utils/route/types'; import { SecurityPageName } from '../../../../app/types'; -import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; import { NetworkRouteType } from '../navigation/types'; +import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; -export const type = networkModel.NetworkType.details; const TabNameMappedToI18nKey: Record = { [NetworkDetailsRouteType.events]: i18n.NAVIGATION_EVENTS_TITLE, [NetworkDetailsRouteType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE, @@ -28,11 +26,15 @@ const TabNameMappedToI18nKey: Record { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( + params, + getSecuritySolutionUrl +) => { let breadcrumb: ChromeBreadcrumb[] = []; if (params.detailName != null) { diff --git a/x-pack/plugins/security_solution/public/explore/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/network/pages/details/index.tsx index b3582262a691a..70f3dc56887fe 100644 --- a/x-pack/plugins/security_solution/public/explore/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/pages/details/index.tsx @@ -58,8 +58,6 @@ import { SecurityCellActionsTrigger, } from '../../../../common/components/cell_actions'; -export { getTrailingBreadcrumbs } from './utils'; - const NetworkDetailsManage = manageQuery(IpOverview); const NetworkDetailsComponent: React.FC = () => { diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/details/utils.ts b/x-pack/plugins/security_solution/public/explore/users/pages/details/breadcrumbs.ts similarity index 77% rename from x-pack/plugins/security_solution/public/explore/users/pages/details/utils.ts rename to x-pack/plugins/security_solution/public/explore/users/pages/details/breadcrumbs.ts index c4ffe7c84e2a8..d2f793417fe32 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/explore/users/pages/details/breadcrumbs.ts @@ -8,16 +8,13 @@ import { get } from 'lodash/fp'; import type { ChromeBreadcrumb } from '@kbn/core/public'; -import { usersModel } from '../../store'; import { UsersTableType } from '../../store/model'; import { getUsersDetailsUrl } from '../../../../common/components/link_to/redirect_to_users'; import * as i18n from '../translations'; import type { UsersRouteSpyState } from '../../../../common/utils/route/types'; import { SecurityPageName } from '../../../../app/types'; -import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; - -export const type = usersModel.UsersType.details; +import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; const TabNameMappedToI18nKey: Record = { [UsersTableType.allUsers]: i18n.NAVIGATION_ALL_USERS_TITLE, @@ -28,10 +25,15 @@ const TabNameMappedToI18nKey: Record = { [UsersTableType.risk]: i18n.NAVIGATION_RISK_TITLE, }; -export const getTrailingBreadcrumbs = ( - params: UsersRouteSpyState, - getSecuritySolutionUrl: GetSecuritySolutionUrl -): ChromeBreadcrumb[] => { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( + params, + getSecuritySolutionUrl +) => { let breadcrumb: ChromeBreadcrumb[] = []; if (params.detailName != null) { diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx index 0046d9aa6f61f..3612fd784d518 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx @@ -41,7 +41,6 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { UsersDetailsTabs } from './details_tabs'; import { navTabsUsersDetails } from './nav_tabs'; import type { UsersDetailsProps } from './types'; -import { type } from './utils'; import { getUsersDetailsPageFilters } from './helpers'; import { showGlobalFilters } from '../../../../timelines/components/timeline/helpers'; import { useGlobalFullScreen } from '../../../../common/containers/use_full_screen'; @@ -257,7 +256,7 @@ const UsersDetailsComponent: React.FC = ({ userDetailFilter={usersDetailsPageFilters} setQuery={setQuery} to={to} - type={type} + type={UsersType.details} usersDetailsPagePath={usersDetailsPagePath} /> diff --git a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx index d3f035ce64065..d0980141ebfcc 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx @@ -17,14 +17,8 @@ import { useHostIsolationTools } from '../../timelines/components/side_panel/eve */ export const PanelFooter: FC = memo(() => { const { closeFlyout } = useExpandableFlyoutContext(); - const { - eventId, - indexName, - dataFormattedForFieldBrowser, - dataAsNestedObject, - refetchFlyoutData, - scopeId, - } = useRightPanelContext(); + const { dataFormattedForFieldBrowser, dataAsNestedObject, refetchFlyoutData, scopeId } = + useRightPanelContext(); const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolationTools(); @@ -36,7 +30,6 @@ export const PanelFooter: FC = memo(() => { { +/** + * This module should only export this function. + * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. + * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. + */ +export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = (params, getSecuritySolutionUrl) => { let breadcrumb: ChromeBreadcrumb[] = []; if (params.detailName != null) { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx index d229b297f239f..3888af95da704 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx @@ -53,9 +53,10 @@ describe('When using processes action from response actions console', () => { }; const endpointDetailsMock = () => { + const newDate = new Date('2023-04-20T09:37:40.309Z'); const endpointMetadata = new EndpointMetadataGenerator('seed').generateHostInfo({ metadata: { - '@timestamp': new Date('2023-04-20T09:37:40.309Z').getTime(), + '@timestamp': newDate.getTime(), agent: { id: agentId, version: '8.8.0', @@ -69,6 +70,7 @@ describe('When using processes action from response actions console', () => { }, }, }, + last_checkin: newDate.toISOString(), }); useGetEndpointDetailsMock.mockReturnValue({ data: endpointMetadata, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx index e901e9b1a116d..357d0e566e328 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useEffect, useMemo, useCallback } from 'react'; +import React, { memo, useCallback, useEffect, useMemo } from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { v4 as uuidV4 } from 'uuid'; import { i18n } from '@kbn/i18n'; @@ -242,7 +242,7 @@ export const EndpointStatusActionResult = memo< 'xpack.securitySolution.endpointResponseActions.status.lastActive', { defaultMessage: 'Last active' } )} - value={endpointDetails.metadata['@timestamp']} + value={endpointDetails.last_checkin} /> ), diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.test.tsx index 4941fd59686cc..5a1c8bab4c05c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.test.tsx @@ -58,7 +58,7 @@ describe('Responder header endpoint info', () => { ); expect(agentStatus.textContent).toBe(`UnhealthyIsolating`); }); - it('should show last updated time', async () => { + it('should show last checkin time', async () => { const lastUpdated = await renderResult.findByTestId('responderHeaderLastSeen'); expect(lastUpdated).toBeTruthy(); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx index b56746e7890a6..e51989ce0cb7e 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx @@ -9,10 +9,10 @@ import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiText, EuiSkeletonText, - EuiToolTip, EuiSpacer, + EuiText, + EuiToolTip, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; @@ -88,7 +88,7 @@ export const HeaderEndpointInfo = memo(({ endpointId }) id="xpack.securitySolution.responder.header.lastSeen" defaultMessage="Last seen {date}" values={{ - date: , + date: , }} /> diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts index d6497c3516d82..d7f073b2a8338 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts @@ -142,6 +142,7 @@ describe('useGetEndpointsList hook', () => { : item.metadata.Endpoint.status, }, }, + last_checkin: item.last_checkin, }; }), }; @@ -164,9 +165,11 @@ describe('useGetEndpointsList hook', () => { const generator = new EndpointDocGenerator('seed'); const total = 60; const data = Array.from({ length: total }, () => { + const newDate = new Date(); const endpoint = { - metadata: generator.generateHostMetadata(), + metadata: generator.generateHostMetadata(newDate.getTime()), host_status: HostStatus.UNHEALTHY, + last_checkin: newDate.toISOString(), }; generator.updateCommonInfo(); @@ -200,9 +203,11 @@ describe('useGetEndpointsList hook', () => { const generator = new EndpointDocGenerator('seed'); const total = 61; const data = Array.from({ length: total }, () => { + const newDate = new Date(); const endpoint = { - metadata: generator.generateHostMetadata(), + metadata: generator.generateHostMetadata(newDate.getTime()), host_status: HostStatus.UNHEALTHY, + last_checkin: newDate.toISOString(), }; generator.updateCommonInfo(); @@ -229,7 +234,7 @@ describe('useGetEndpointsList hook', () => { .data.map((d) => d.metadata.agent.id) .slice(0, 50); - // call useGetEndpointsList with all 50 agents selected + // call useGetEndpointsList with all 50 agents selected const res = await renderReactQueryHook(() => useGetEndpointsList({ searchString: '', selectedAgentIds: agentIdsToSelect }) ); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts index e3b7bb29ba2b3..fb4270ee6dad5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts @@ -19,11 +19,9 @@ export const initialEndpointPageState = (): Immutable => { loading: false, error: undefined, endpointDetails: { - hostDetails: { - details: undefined, - detailsLoading: false, - detailsError: undefined, - }, + hostInfo: undefined, + hostInfoError: undefined, + isHostInfoLoading: false, }, policyResponse: undefined, policyResponseLoading: false, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index f83b58f57fb12..1524b721cb07c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -44,11 +44,9 @@ describe('EndpointList store concerns', () => { loading: false, error: undefined, endpointDetails: { - hostDetails: { - details: undefined, - detailsLoading: false, - detailsError: undefined, - }, + hostInfo: undefined, + hostInfoError: undefined, + isHostInfoLoading: false, }, policyResponse: undefined, policyResponseLoading: false, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 580a3b761d245..d407a6cc27cce 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -16,22 +16,22 @@ import type { } from '@kbn/timelines-plugin/common'; import { BASE_POLICY_RESPONSE_ROUTE, + ENDPOINT_FIELDS_SEARCH_STRATEGY, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, - metadataCurrentIndexPattern, - METADATA_UNITED_INDEX, METADATA_TRANSFORMS_STATUS_ROUTE, - ENDPOINT_FIELDS_SEARCH_STRATEGY, + METADATA_UNITED_INDEX, + metadataCurrentIndexPattern, } from '../../../../../common/endpoint/constants'; import type { GetHostPolicyResponse, HostInfo, HostIsolationRequestBody, - ResponseActionApiResponse, HostResultList, Immutable, ImmutableObject, MetadataListResponse, + ResponseActionApiResponse, } from '../../../../../common/endpoint/types'; import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isolation'; import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; @@ -59,9 +59,9 @@ import type { } from '../types'; import type { EndpointPackageInfoStateChanged } from './action'; import { - detailsData, endpointPackageInfo, endpointPackageVersion, + fullDetailsHostInfo, getCurrentIsolationRequestState, getIsEndpointPackageInfoUninitialized, getIsIsolationRequestPending, @@ -86,7 +86,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory { // this needs to be called after endpointPackageVersion is loaded (getEndpointPackageInfo) - // or else wrong pattern might be loaded + // or else the wrong pattern might be loaded async function fetchIndexPatterns( state: ImmutableObject ): Promise { @@ -115,6 +115,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory (next) => async (action) => { next(action); @@ -329,13 +330,13 @@ const loadEndpointsPendingActions = async ({ dispatch, }: EndpointPageStore): Promise => { const state = getState(); - const detailsEndpoint = detailsData(state); + const detailsEndpoint = fullDetailsHostInfo(state); const listEndpoints = listData(state); const agentsIds = new Set(); // get all agent ids for the endpoints in the list if (detailsEndpoint) { - agentsIds.add(detailsEndpoint.elastic.agent.id); + agentsIds.add(detailsEndpoint.metadata.elastic.agent.id); } for (const endpointInfo of listEndpoints) { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index 671347dcd27b3..f6c5c144f529b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -7,11 +7,11 @@ import type { HttpStart } from '@kbn/core/public'; import type { + BulkGetPackagePoliciesResponse, GetAgentPoliciesResponse, GetAgentPoliciesResponseItem, - GetPackagesResponse, GetAgentsResponse, - BulkGetPackagePoliciesResponse, + GetPackagesResponse, } from '@kbn/fleet-plugin/common/types/rest_spec'; import type { GetHostPolicyResponse, @@ -25,8 +25,8 @@ import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_da import { INGEST_API_AGENT_POLICIES, INGEST_API_EPM_PACKAGES, - INGEST_API_PACKAGE_POLICIES, INGEST_API_FLEET_AGENTS, + INGEST_API_PACKAGE_POLICIES, } from '../../../services/policies/ingest'; import type { GetPolicyListResponse } from '../../policy/types'; import { pendingActionsResponseMock } from '../../../../common/lib/endpoint_pending_actions/mocks'; @@ -54,9 +54,12 @@ export const mockEndpointResultList: (options?: { const hosts: HostInfo[] = []; for (let index = 0; index < actualCountToReturn; index++) { + const newDate = new Date(); + const metadata = generator.generateHostMetadata(newDate.getTime()); hosts.push({ - metadata: generator.generateHostMetadata(), + metadata, host_status: HostStatus.UNHEALTHY, + last_checkin: newDate.toISOString(), }); } const mock: MetadataListResponse = { @@ -72,9 +75,12 @@ export const mockEndpointResultList: (options?: { * returns a mocked API response for retrieving a single host metadata */ export const mockEndpointDetailsApiResult = (): HostInfo => { + const newDate = new Date(); + const metadata = generator.generateHostMetadata(newDate.getTime()); return { - metadata: generator.generateHostMetadata(), + metadata, host_status: HostStatus.UNHEALTHY, + last_checkin: newDate.toISOString(), }; }; @@ -118,8 +124,8 @@ const endpointListApiPathHandlerMocks = ({ }; }, - // Do policies referenced in endpoint list exist - // just returns 1 single agent policy that includes all of the packagePolicy IDs provided + // Do policies reference in endpoint list exist + // just returns 1 single agent policy that includes all the packagePolicy IDs provided [INGEST_API_AGENT_POLICIES]: (): GetAgentPoliciesResponse => { return { items: [agentPolicy], @@ -184,7 +190,7 @@ const endpointListApiPathHandlerMocks = ({ }; /** - * Sets up mock impelementations in support of the Endpoints list view + * Sets up mock implementations in support of the Endpoints list view * * @param mockedHttpService * @param endpointsResults diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 8ad781c60dd20..db15ebcfa7343 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -11,11 +11,11 @@ import type { MetadataTransformStatsChanged, } from './action'; import { - isOnEndpointPage, + getCurrentIsolationRequestState, + getIsOnEndpointDetailsActivityLog, hasSelectedEndpoint, + isOnEndpointPage, uiQueryParams, - getIsOnEndpointDetailsActivityLog, - getCurrentIsolationRequestState, } from './selectors'; import type { EndpointState } from '../types'; import { initialEndpointPageState } from './builders'; @@ -97,7 +97,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta }, }; } else if (action.type === 'serverReturnedMetadataPatterns') { - // handle error case + // handle an error case return { ...state, patterns: action.payload, @@ -114,12 +114,8 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta endpointDetails: { ...state.endpointDetails, hostInfo: action.payload, - hostDetails: { - ...state.endpointDetails.hostDetails, - details: action.payload.metadata, - detailsLoading: false, - detailsError: undefined, - }, + hostInfoError: undefined, + isHostInfoLoading: false, }, policyVersionInfo: action.payload.policy_info, hostStatus: action.payload.host_status, @@ -129,11 +125,8 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta ...state, endpointDetails: { ...state.endpointDetails, - hostDetails: { - ...state.endpointDetails.hostDetails, - detailsError: action.payload, - detailsLoading: false, - }, + hostInfoError: action.payload, + isHostInfoLoading: false, }, }; } else if (action.type === 'endpointPendingActionsStateChanged') { @@ -262,44 +255,35 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta ...stateUpdates, endpointDetails: { ...state.endpointDetails, - hostDetails: { - ...state.endpointDetails.hostDetails, - detailsError: undefined, - }, + hostInfoError: undefined, }, loading: true, policyItemsLoading: true, }; } } else if (isCurrentlyOnDetailsPage) { - // if previous page was the list or another endpoint details page, load endpoint details only + // if the previous page was the list or another endpoint details page, load endpoint details only if (wasPreviouslyOnDetailsPage || wasPreviouslyOnListPage) { return { ...state, ...stateUpdates, endpointDetails: { ...state.endpointDetails, - hostDetails: { - ...state.endpointDetails.hostDetails, - detailsLoading: !isNotLoadingDetails, - detailsError: undefined, - }, + hostInfoError: undefined, + isHostInfoLoading: !isNotLoadingDetails, }, detailsLoading: true, policyResponseLoading: true, }; } else { - // if previous page was not endpoint list or endpoint details, load both list and details + // if the previous page was not endpoint list or endpoint details, load both list and details return { ...state, ...stateUpdates, endpointDetails: { ...state.endpointDetails, - hostDetails: { - ...state.endpointDetails.hostDetails, - detailsLoading: true, - detailsError: undefined, - }, + hostInfoError: undefined, + isHostInfoLoading: true, }, loading: true, policyResponseLoading: true, @@ -307,16 +291,13 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta }; } } - // otherwise we are not on a endpoint list or details page + // otherwise, we are not on an endpoint list or details page return { ...state, ...stateUpdates, endpointDetails: { ...state.endpointDetails, - hostDetails: { - ...state.endpointDetails.hostDetails, - detailsError: undefined, - }, + hostInfoError: undefined, }, endpointsExist: true, }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 1b4c716c37462..c01a7ea65a8d6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -11,9 +11,9 @@ import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; import { decode } from '@kbn/rison'; import type { Query } from '@kbn/es-query'; -import type { Immutable, EndpointPendingActions } from '../../../../../common/endpoint/types'; +import type { EndpointPendingActions, Immutable } from '../../../../../common/endpoint/types'; import { HostStatus } from '../../../../../common/endpoint/types'; -import type { EndpointState, EndpointIndexUIQueryParams } from '../types'; +import type { EndpointIndexUIQueryParams, EndpointState } from '../types'; import { extractListPaginationParams } from '../../../common/routing'; import { MANAGEMENT_DEFAULT_PAGE, @@ -43,26 +43,23 @@ export const listLoading = (state: Immutable): boolean => state.l export const listError = (state: Immutable) => state.error; -export const detailsData = (state: Immutable) => - state.endpointDetails.hostDetails.details; - -export const fullDetailsHostInfo = (state: Immutable) => - state.endpointDetails.hostInfo; +export const fullDetailsHostInfo = ( + state: Immutable +): EndpointState['endpointDetails']['hostInfo'] => state.endpointDetails.hostInfo; -export const detailsLoading = (state: Immutable): boolean => - state.endpointDetails.hostDetails.detailsLoading; +export const isHostInfoLoading = ( + state: Immutable +): EndpointState['endpointDetails']['isHostInfoLoading'] => state.endpointDetails.isHostInfoLoading; -export const detailsError = ( +export const hostInfoError = ( state: Immutable -): EndpointState['endpointDetails']['hostDetails']['detailsError'] => - state.endpointDetails.hostDetails.detailsError; +): EndpointState['endpointDetails']['hostInfoError'] => state.endpointDetails.hostInfoError; export const policyItems = (state: Immutable) => state.policyItems; export const policyItemsLoading = (state: Immutable) => state.policyItemsLoading; export const selectedPolicyId = (state: Immutable) => state.selectedPolicyId; - export const endpointPackageInfo = (state: Immutable) => state.endpointPackageInfo; export const getIsEndpointPackageInfoUninitialized: (state: Immutable) => boolean = createSelector(endpointPackageInfo, (packageInfo) => isUninitialisedResourceState(packageInfo)); @@ -258,8 +255,8 @@ export const getIsOnEndpointDetailsActivityLog: (state: Immutable return searchParams.show === EndpointDetailsTabsTypes.activityLog; }); -export const getIsEndpointHostIsolated = createSelector(detailsData, (details) => { - return (details && isEndpointHostIsolated(details)) || false; +export const getIsEndpointHostIsolated = createSelector(fullDetailsHostInfo, (details) => { + return (details && isEndpointHostIsolated(details.metadata)) || false; }); export const getEndpointPendingActionsState = ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index cdd5020226697..c7de43f6bc374 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -8,15 +8,14 @@ import type { DataViewBase } from '@kbn/es-query'; import type { GetInfoResponse } from '@kbn/fleet-plugin/common'; import type { + AppLocation, + EndpointPendingActions, HostInfo, - Immutable, - HostMetadata, HostPolicyResponse, - AppLocation, - PolicyData, HostStatus, + Immutable, + PolicyData, ResponseActionApiResponse, - EndpointPendingActions, } from '../../../../common/endpoint/types'; import type { ServerApiError } from '../../../common/types'; import type { AsyncResourceState } from '../../state'; @@ -39,14 +38,8 @@ export interface EndpointState { // Adding `hostInfo` to store full API response in order to support the // refactoring effort with AgentStatus component hostInfo?: HostInfo; - hostDetails: { - /** details data for a specific host */ - details?: Immutable; - /** details page is retrieving data */ - detailsLoading: boolean; - /** api error from retrieving host details */ - detailsError?: ServerApiError; - }; + hostInfoError?: ServerApiError; + isHostInfoLoading: boolean; }; /** Holds the Policy Response for the Host currently being displayed in the details */ policyResponse?: HostPolicyResponse; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.tsx index 69a4dd2054e6c..7942b10059bcb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.tsx @@ -9,12 +9,12 @@ import React, { useState, useCallback, useMemo } from 'react'; import { EuiContextMenuPanel, EuiButton, EuiPopover } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useEndpointActionItems, useEndpointSelector } from '../../hooks'; -import { detailsData } from '../../../store/selectors'; +import { fullDetailsHostInfo } from '../../../store/selectors'; import { ContextMenuItemNavByRouter } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router'; export const ActionsMenu = React.memo<{}>(() => { - const endpointDetails = useEndpointSelector(detailsData); - const menuOptions = useEndpointActionItems(endpointDetails); + const endpointDetails = useEndpointSelector(fullDetailsHostInfo); + const menuOptions = useEndpointActionItems(endpointDetails?.metadata); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopoverHandler = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/flyout_header.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/flyout_header.tsx index 8cc41f19e94a3..256b606ad5110 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/flyout_header.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/flyout_header.tsx @@ -6,9 +6,9 @@ */ import React, { memo } from 'react'; -import { EuiFlyoutHeader, EuiSkeletonText, EuiToolTip, EuiTitle } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiSkeletonText, EuiTitle, EuiToolTip } from '@elastic/eui'; import { useEndpointSelector } from '../../hooks'; -import { detailsLoading } from '../../../store/selectors'; +import { isHostInfoLoading } from '../../../store/selectors'; import { BackToEndpointDetailsFlyoutSubHeader } from './back_to_endpoint_details_flyout_subheader'; export const EndpointDetailsFlyoutHeader = memo( @@ -21,9 +21,9 @@ export const EndpointDetailsFlyoutHeader = memo( endpointId?: string; hasBorder?: boolean; hostname?: string; - children?: React.ReactNode | React.ReactNodeArray; + children?: React.ReactNode | React.ReactNode[]; }) => { - const hostDetailsLoading = useEndpointSelector(detailsLoading); + const hostDetailsLoading = useEndpointSelector(isHostInfoLoading); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx index b52aaa0da4223..a7f02fbb5da03 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx @@ -14,9 +14,8 @@ import type { HostMetadata } from '../../../../../../common/endpoint/types'; import { useToasts } from '../../../../../common/lib/kibana'; import { getEndpointDetailsPath } from '../../../../common/routing'; import { - detailsData, - detailsError, - hostStatusInfo, + fullDetailsHostInfo, + hostInfoError, policyVersionInfo, showView, uiQueryParams, @@ -26,8 +25,8 @@ import * as i18 from '../translations'; import { ActionsMenu } from './components/actions_menu'; import { EndpointDetailsFlyoutTabs, - EndpointDetailsTabsTypes, type EndpointDetailsTabs, + EndpointDetailsTabsTypes, } from './components/endpoint_details_tabs'; import { EndpointIsolationFlyoutPanel } from './components/endpoint_isolate_flyout_panel'; import { EndpointDetailsFlyoutHeader } from './components/flyout_header'; @@ -37,11 +36,10 @@ export const EndpointDetails = memo(() => { const toasts = useToasts(); const queryParams = useEndpointSelector(uiQueryParams); - const hostDetails = useEndpointSelector(detailsData); - const hostDetailsError = useEndpointSelector(detailsError); + const hostInfo = useEndpointSelector(fullDetailsHostInfo); + const hostDetailsError = useEndpointSelector(hostInfoError); const policyInfo = useEndpointSelector(policyVersionInfo); - const hostStatus = useEndpointSelector(hostStatusInfo); const show = useEndpointSelector(showView); const { canAccessEndpointActionsLogManagement } = useUserPrivileges().endpointPrivileges; @@ -68,14 +66,10 @@ export const EndpointDetails = memo(() => { selected_endpoint: id, }), content: - hostDetails === undefined ? ( + hostInfo === undefined ? ( ContentLoadingMarkup ) : ( - + ), }, ]; @@ -96,14 +90,7 @@ export const EndpointDetails = memo(() => { } return tabs; }, - [ - canAccessEndpointActionsLogManagement, - ContentLoadingMarkup, - hostDetails, - policyInfo, - hostStatus, - queryParams, - ] + [canAccessEndpointActionsLogManagement, ContentLoadingMarkup, hostInfo, policyInfo, queryParams] ); const showFlyoutFooter = @@ -127,11 +114,11 @@ export const EndpointDetails = memo(() => { {(show === 'policy_response' || show === 'isolate' || show === 'unisolate') && ( )} - {hostDetails === undefined ? ( + {hostInfo === undefined ? ( @@ -139,18 +126,18 @@ export const EndpointDetails = memo(() => { <> {(show === 'details' || show === 'activity_log') && ( )} - {show === 'policy_response' && } + {show === 'policy_response' && } {(show === 'isolate' || show === 'unisolate') && ( - + )} {showFlyoutFooter && ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx index b33f98078b9fb..13878c9377eb4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx @@ -8,21 +8,20 @@ import styled from 'styled-components'; import { EuiDescriptionList, - EuiText, EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiLink, EuiHealth, + EuiLink, + EuiSpacer, + EuiText, } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EndpointAgentStatus } from '../../../../../common/components/endpoint/endpoint_agent_status'; import { isPolicyOutOfDate } from '../../utils'; -import type { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types'; +import type { HostInfo } from '../../../../../../common/endpoint/types'; import { useEndpointSelector } from '../hooks'; import { - fullDetailsHostInfo, getEndpointPendingActionsCallback, nonExistingPolicies, policyResponseStatus, @@ -39,9 +38,11 @@ const EndpointDetailsContentStyled = styled.div` dl dt { max-width: 27%; } + dl dd { max-width: 73%; } + .policyLineText { padding-right: 5px; } @@ -55,34 +56,28 @@ const ColumnTitle = ({ children }: { children: React.ReactNode }) => { ); }; -export const EndpointDetailsContent = memo( - ({ - details, - policyInfo, - hostStatus, - }: { - details: HostMetadata; - policyInfo?: HostInfo['policy_info']; - hostStatus: HostStatus; - }) => { +interface EndpointDetailsContentProps { + hostInfo: HostInfo; + policyInfo?: HostInfo['policy_info']; +} + +export const EndpointDetailsContent = memo( + ({ hostInfo, policyInfo }) => { const queryParams = useEndpointSelector(uiQueryParams); const policyStatus = useEndpointSelector( policyResponseStatus ) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR; const getHostPendingActions = useEndpointSelector(getEndpointPendingActionsCallback); const missingPolicies = useEndpointSelector(nonExistingPolicies); - const hostInfo = useEndpointSelector(fullDetailsHostInfo); const policyResponseRoutePath = useMemo(() => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { selected_endpoint, show, ...currentUrlParams } = queryParams; - const path = getEndpointDetailsPath({ + const { selected_endpoint: selectedEndpoint, show, ...currentUrlParams } = queryParams; + return getEndpointDetailsPath({ name: 'endpointPolicyResponse', ...currentUrlParams, - selected_endpoint: details.agent.id, + selected_endpoint: hostInfo.metadata.agent.id, }); - return path; - }, [details.agent.id, queryParams]); + }, [hostInfo.metadata.agent.id, queryParams]); const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath); @@ -97,7 +92,7 @@ export const EndpointDetailsContent = memo( /> ), - description: {details.host.os.full}, + description: {hostInfo.metadata.host.os.full}, }, { title: ( @@ -108,13 +103,11 @@ export const EndpointDetailsContent = memo( /> ), - description: hostInfo ? ( + description: ( - ) : ( - <> ), }, { @@ -128,7 +121,10 @@ export const EndpointDetailsContent = memo( ), description: ( - + ), }, @@ -144,14 +140,14 @@ export const EndpointDetailsContent = memo( description: ( - {details.Endpoint.policy.applied.name} + {hostInfo.metadata.Endpoint.policy.applied.name} - {details.Endpoint.policy.applied.endpoint_policy_version && ( + {hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version && ( )} - {isPolicyOutOfDate(details.Endpoint.policy.applied, policyInfo) && } + {isPolicyOutOfDate(hostInfo.metadata.Endpoint.policy.applied, policyInfo) && ( + + )} ), }, @@ -206,7 +204,7 @@ export const EndpointDetailsContent = memo( /> ), - description: {details.agent.version}, + description: {hostInfo.metadata.agent.version}, }, { title: ( @@ -219,7 +217,7 @@ export const EndpointDetailsContent = memo( ), description: ( - {details.host.ip.map((ip: string, index: number) => ( + {hostInfo.metadata.host.ip.map((ip: string, index: number) => ( {ip} @@ -229,9 +227,8 @@ export const EndpointDetailsContent = memo( }, ]; }, [ - details, - getHostPendingActions, hostInfo, + getHostPendingActions, missingPolicies, policyInfo, policyStatus, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 9b2e681e4f4be..027f2cc20780d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -48,8 +48,8 @@ import { import type { TransformStats } from '../types'; import { HOST_METADATA_LIST_ROUTE, - metadataTransformPrefix, METADATA_UNITED_TRANSFORM, + metadataTransformPrefix, } from '../../../../../common/endpoint/constants'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { @@ -62,7 +62,7 @@ import { getEndpointPrivilegesInitialStateMock } from '../../../../common/compon const mockUserPrivileges = useUserPrivileges as jest.Mock; // not sure why this can't be imported from '../../../../common/mock/formatted_relative'; -// but sure enough it needs to be inline in this one file +// but sure enough, it needs to be inline in this one file jest.mock('@kbn/i18n-react', () => { const originalModule = jest.requireActual('@kbn/i18n-react'); const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); @@ -310,6 +310,7 @@ describe('when on the endpoint list page', () => { hostListData[index].metadata.Endpoint.policy.applied, setup.policy ), + last_checkin: hostListData[index].last_checkin, }; }); hostListData.forEach((item, index) => { @@ -485,17 +486,17 @@ describe('when on the endpoint list page', () => { }); describe('when there is a selected host in the url', () => { - let hostDetails: HostInfo; + let hostInfo: HostInfo; let renderAndWaitForData: () => Promise>; const mockEndpointListApi = (mockedPolicyResponse?: HostPolicyResponse) => { const { - // eslint-disable-next-line @typescript-eslint/naming-convention - host_status, + host_status: hostStatus, + last_checkin: lastCheckin, metadata: { agent, Endpoint, ...details }, } = mockEndpointDetailsApiResult(); - hostDetails = { - host_status, + hostInfo = { + host_status: hostStatus, metadata: { ...details, Endpoint: { @@ -510,13 +511,14 @@ describe('when on the endpoint list page', () => { id: '1', }, }, + last_checkin: lastCheckin, }; const policy = docGenerator.generatePolicyPackagePolicy(); - policy.id = hostDetails.metadata.Endpoint.policy.applied.id; + policy.id = hostInfo.metadata.Endpoint.policy.applied.id; setEndpointListApiMockImplementation(coreStart.http, { - endpointsResults: [hostDetails], + endpointsResults: [hostInfo], endpointPackagePolicies: [policy], policyResponse: mockedPolicyResponse, }); @@ -617,7 +619,7 @@ describe('when on the endpoint list page', () => { const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); expect(policyDetailsLink).not.toBeNull(); expect(policyDetailsLink.getAttribute('href')).toEqual( - `${APP_PATH}${MANAGEMENT_PATH}/policy/${hostDetails.metadata.Endpoint.policy.applied.id}/settings` + `${APP_PATH}${MANAGEMENT_PATH}/policy/${hostInfo.metadata.Endpoint.policy.applied.id}/settings` ); }); @@ -626,7 +628,7 @@ describe('when on the endpoint list page', () => { const policyDetailsRevElement = await renderResult.findByTestId('policyDetailsRevNo'); expect(policyDetailsRevElement).not.toBeNull(); expect(policyDetailsRevElement.textContent).toEqual( - `rev. ${hostDetails.metadata.Endpoint.policy.applied.endpoint_policy_version}` + `rev. ${hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version}` ); }); @@ -639,7 +641,7 @@ describe('when on the endpoint list page', () => { }); const changedUrlAction = await userChangedUrlChecker; expect(changedUrlAction.payload.pathname).toEqual( - `${MANAGEMENT_PATH}/policy/${hostDetails.metadata.Endpoint.policy.applied.id}/settings` + `${MANAGEMENT_PATH}/policy/${hostInfo.metadata.Endpoint.policy.applied.id}/settings` ); }); @@ -1019,6 +1021,7 @@ describe('when on the endpoint list page', () => { version: '7.14.0', }, }, + last_checkin: hosts[0].last_checkin, }, { host_status: hosts[1].host_status, @@ -1044,6 +1047,7 @@ describe('when on the endpoint list page', () => { version: '8.4.0', }, }, + last_checkin: hosts[1].last_checkin, }, ]; @@ -1333,7 +1337,7 @@ describe('when on the endpoint list page', () => { beforeEach(async () => { const { data: hosts } = mockEndpointResultList({ total: 2 }); - // second host is isolated, for unisolate testing + // the second host is isolated, for unisolate testing const hostInfo: HostInfo[] = [ { host_status: hosts[0].host_status, @@ -1359,6 +1363,7 @@ describe('when on the endpoint list page', () => { version: '7.14.0', }, }, + last_checkin: hosts[0].last_checkin, }, { host_status: hosts[1].host_status, @@ -1384,6 +1389,7 @@ describe('when on the endpoint list page', () => { version: '8.4.0', }, }, + last_checkin: hosts[1].last_checkin, }, ]; setEndpointListApiMockImplementation(coreStart.http, { diff --git a/x-pack/plugins/security_solution/public/mocks.ts b/x-pack/plugins/security_solution/public/mocks.ts index f16e81636846c..0a1072f5fd22f 100644 --- a/x-pack/plugins/security_solution/public/mocks.ts +++ b/x-pack/plugins/security_solution/public/mocks.ts @@ -6,6 +6,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import type { BreadcrumbsNav } from './common/breadcrumbs'; import type { NavigationLink } from './common/links/types'; const setupMock = () => ({ @@ -15,6 +16,10 @@ const setupMock = () => ({ const startMock = () => ({ getNavLinks$: jest.fn(() => new BehaviorSubject([])), setIsSidebarEnabled: jest.fn(), + setGetStartedPage: jest.fn(), + getBreadcrumbsNav$: jest.fn( + () => new BehaviorSubject({ leading: [], trailing: [] }) + ), }); export const securitySolutionMock = { diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 929d38a445cd8..70c1cb7667752 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { BehaviorSubject, Subject } from 'rxjs'; +import { Subject } from 'rxjs'; import type * as H from 'history'; import type { AppMountParameters, @@ -36,7 +36,6 @@ import { APP_ID, APP_UI_ID, APP_PATH, APP_ICON_SOLUTION } from '../common/consta import { updateAppLinks, type LinksPermissions } from './common/links'; import { registerDeepLinksUpdater } from './common/links/deep_links'; -import { navLinks$ } from './common/links/nav_links'; import { licenseService } from './common/hooks/use_license'; import type { SecuritySolutionUiConfigType } from './common/types'; import { ExperimentalFeaturesService } from './common/experimental_features_service'; @@ -49,10 +48,10 @@ import { getLazyEndpointPolicyResponseExtension } from './management/pages/polic import { getLazyEndpointGenericErrorsListExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list'; import type { ExperimentalFeatures } from '../common/experimental_features'; import { parseExperimentalConfigValue } from '../common/experimental_features'; -import { UpsellingService } from './common/lib/upsellings'; import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension'; import type { SecurityAppStore } from './common/store/types'; +import { PluginContract } from './plugin_contract'; export class Plugin implements IPlugin { /** @@ -76,12 +75,10 @@ export class Plugin implements IPlugin; - private getStartedComponent$: BehaviorSubject; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); @@ -91,9 +88,7 @@ export class Plugin implements IPlugin(true); - this.getStartedComponent$ = new BehaviorSubject(null); - this.upsellingService = new UpsellingService(); + this.contract = new PluginContract(); this.telemetry = new TelemetryService(); } private appUpdater$ = new Subject(); @@ -158,6 +153,7 @@ export class Plugin implements IPlugin SecuritySolutionTemplateWrapper, }, savedObjectsManagement: startPluginsDeps.savedObjectsManagement, - isSidebarEnabled$: this.isSidebarEnabled$, - getStartedComponent$: this.getStartedComponent$, - upselling: this.upsellingService, telemetry: this.telemetry.start(), }; return services; @@ -235,19 +228,7 @@ export class Plugin implements IPlugin { - /** - * The specially formatted comment in the `import` expression causes the corresponding webpack chunk to be named. This aids us in debugging chunk size issues. - * See https://webpack.js.org/api/module-methods/#magic-comments - */ - const { resolverPluginSetup } = await import( - /* webpackChunkName: "resolver" */ './resolver' - ); - return resolverPluginSetup(); - }, - upselling: this.upsellingService, - }; + return this.contract.getSetupContract(); } public start(core: CoreStart, plugins: StartPlugins): PluginStart { @@ -310,19 +291,12 @@ export class Plugin implements IPlugin navLinks$, - setIsSidebarEnabled: (isSidebarEnabled: boolean) => - this.isSidebarEnabled$.next(isSidebarEnabled), - setGetStartedPage: (getStartedComponent) => { - this.getStartedComponent$.next(getStartedComponent); - }, - }; + return this.contract.getStartContract(); } public stop() { licenseService.stop(); - return {}; + return this.contract.getStopContract(); } private lazyHelpersForRoutes() { @@ -492,13 +466,14 @@ export class Plugin implements IPlugin { const linksPermissions: LinksPermissions = { experimentalFeatures: this.experimentalFeatures, - upselling: this.upsellingService, + upselling, capabilities: core.application.capabilities, }; diff --git a/x-pack/plugins/security_solution/public/plugin_contract.ts b/x-pack/plugins/security_solution/public/plugin_contract.ts new file mode 100644 index 0000000000000..583424509e131 --- /dev/null +++ b/x-pack/plugins/security_solution/public/plugin_contract.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 { BehaviorSubject } from 'rxjs'; +import { UpsellingService } from './common/lib/upsellings'; +import type { ContractStartServices, PluginSetup, PluginStart } from './types'; +import { navLinks$ } from './common/links/nav_links'; +import { breadcrumbsNav$ } from './common/breadcrumbs'; + +export class PluginContract { + public isSidebarEnabled$: BehaviorSubject; + public getStartedComponent$: BehaviorSubject; + public upsellingService: UpsellingService; + + constructor() { + this.isSidebarEnabled$ = new BehaviorSubject(true); + this.getStartedComponent$ = new BehaviorSubject(null); + this.upsellingService = new UpsellingService(); + } + + public getStartServices(): ContractStartServices { + return { + isSidebarEnabled$: this.isSidebarEnabled$.asObservable(), + getStartedComponent$: this.getStartedComponent$.asObservable(), + upselling: this.upsellingService, + }; + } + + public getSetupContract(): PluginSetup { + return { + resolver: lazyResolver, + upselling: this.upsellingService, + }; + } + + public getStartContract(): PluginStart { + return { + getNavLinks$: () => navLinks$, + setIsSidebarEnabled: (isSidebarEnabled: boolean) => + this.isSidebarEnabled$.next(isSidebarEnabled), + setGetStartedPage: (getStartedComponent) => { + this.getStartedComponent$.next(getStartedComponent); + }, + getBreadcrumbsNav$: () => breadcrumbsNav$, + }; + } + + public getStopContract() { + return {}; + } +} + +const lazyResolver = async () => { + /** + * The specially formatted comment in the `import` expression causes the corresponding webpack chunk to be named. This aids us in debugging chunk size issues. + * See https://webpack.js.org/api/module-methods/#magic-comments + */ + const { resolverPluginSetup } = await import( + /* webpackChunkName: "resolver" */ + './resolver' + ); + return resolverPluginSetup(); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 5f4e7a087b02e..97b36e4f8c0c9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -220,7 +220,6 @@ export const ExpandableEvent = React.memo( detailsEcsData={detailsEcsData} id={event.eventId} isAlert={isAlert} - indexName={event.indexName} isDraggable={isDraggable} rawEventData={rawEventData} scopeId={scopeId} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx index 6f069cb8dc83e..ab24a16ca1a93 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx @@ -115,7 +115,6 @@ const defaultProps = { isHostIsolationPanelOpen: false, handleOnEventClosed: jest.fn(), onAddIsolationStatusClick: jest.fn(), - expandedEvent: { eventId: ecsData._id, indexName: '' }, detailsData: mockAlertDetailsDataWithIsObject, refetchFlyoutData: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index b91f01885e9d5..9234a05bf1f07 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -26,11 +26,6 @@ import { OsqueryFlyout } from '../../../../../detections/components/osquery/osqu interface FlyoutFooterProps { detailsData: TimelineEventsDetailsItem[] | null; detailsEcsData: Ecs | null; - expandedEvent: { - eventId: string; - indexName: string; - refetch?: () => void; - }; handleOnEventClosed: () => void; isHostIsolationPanelOpen: boolean; isReadOnly?: boolean; @@ -52,7 +47,6 @@ export const FlyoutFooterComponent = React.memo( ({ detailsData, detailsEcsData, - expandedEvent, handleOnEventClosed, isHostIsolationPanelOpen, isReadOnly, @@ -162,7 +156,6 @@ export const FlyoutFooterComponent = React.memo( onAddIsolationStatusClick={onAddIsolationStatusClick} refetchFlyoutData={refetchFlyoutData} refetch={refetchAll} - indexName={expandedEvent.indexName} scopeId={scopeId} onOsqueryClick={setOsqueryFlyoutOpenWithAgentId} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx index 2a9f30d0ec29b..d52fa2331e016 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx @@ -136,7 +136,6 @@ export const useToGetInternalFlyout = () => { = ({ ({ @@ -309,7 +309,7 @@ export const useSessionView = ({ loadAlertDetails: openEventDetailsPanel, isFullScreen: fullScreen, height: heightMinusSearchBar, - canAccessEndpointManagement, + canReadPolicyManagement, }) : null; }, [ @@ -318,7 +318,7 @@ export const useSessionView = ({ sessionView, openEventDetailsPanel, fullScreen, - canAccessEndpointManagement, + canReadPolicyManagement, ]); return { diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 2687c1fcab25e..31765d2395176 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { BehaviorSubject, Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; import type { AppLeaveHandler, CoreStart } from '@kbn/core/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; @@ -70,6 +70,7 @@ import type { NavigationLink } from './common/links'; import type { TelemetryClientStart } from './common/lib/telemetry'; import type { Dashboards } from './dashboards'; import type { UpsellingService } from './common/lib/upsellings'; +import type { BreadcrumbsNav } from './common/breadcrumbs/types'; export interface SetupPlugins { cloud?: CloudSetup; @@ -127,8 +128,15 @@ export interface StartPluginsDependencies extends StartPlugins { savedObjectsTaggingOss: SavedObjectTaggingOssPluginStart; } +export interface ContractStartServices { + isSidebarEnabled$: Observable; + getStartedComponent$: Observable; + upselling: UpsellingService; +} + export type StartServices = CoreStart & - StartPlugins & { + StartPlugins & + ContractStartServices & { storage: Storage; sessionStorage: Storage; apm: ApmBase; @@ -143,9 +151,6 @@ export type StartServices = CoreStart & getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper; }; savedObjectsManagement: SavedObjectsManagementPluginStart; - isSidebarEnabled$: BehaviorSubject; - getStartedComponent$: BehaviorSubject; - upselling: UpsellingService; telemetry: TelemetryClientStart; }; @@ -158,6 +163,7 @@ export interface PluginStart { getNavLinks$: () => Observable; setIsSidebarEnabled: (isSidebarEnabled: boolean) => void; setGetStartedPage: (getStartedComponent: React.ComponentType) => void; + getBreadcrumbsNav$: () => Observable; } export interface AppObservableLibs { diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts index f8c3aad63f4e2..8a5c77c2a61b0 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts @@ -209,8 +209,9 @@ export const sendEndpointActionResponse = async ( const fileMeta = await esClient.index({ index: FILE_STORAGE_METADATA_INDEX, id: getFileDownloadId(action, action.agents[0]), - body: fileMetaDoc, + op_type: 'create', refresh: 'wait_for', + body: fileMetaDoc, }); // Index the file content (just one chunk) @@ -224,12 +225,14 @@ export const sendEndpointActionResponse = async ( document: cborx.encode({ bid: fileMeta._id, last: true, + '@timestamp': new Date().toISOString(), data: Buffer.from( 'UEsDBAoACQAAAFZeRFWpAsDLHwAAABMAAAAMABwAYmFkX2ZpbGUudHh0VVQJAANTVjxjU1Y8Y3V4CwABBPUBAAAEFAAAAMOcoyEq/Q4VyG02U9O0LRbGlwP/y5SOCfRKqLz1rsBQSwcIqQLAyx8AAAATAAAAUEsBAh4DCgAJAAAAVl5EVakCwMsfAAAAEwAAAAwAGAAAAAAAAQAAAKSBAAAAAGJhZF9maWxlLnR4dFVUBQADU1Y8Y3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAFIAAAB1AAAAAAA=', 'base64' ), }), refresh: 'wait_for', + op_type: 'create', }, { headers: { diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts index 434df1b209a19..e38725e5d5e6e 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts @@ -44,7 +44,7 @@ export interface RuntimeServices { interface CreateRuntimeServicesOptions { kibanaUrl: string; elasticsearchUrl: string; - fleetServerUrl: string | undefined; + fleetServerUrl?: string; username: string; password: string; log?: ToolingLog; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts index d1818b9b494e7..a4536f54b92d7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts @@ -214,6 +214,7 @@ export const generateFileMetadataDocumentMock = ( transithash: { sha256: 'a0d6d6a2bb73340d4a0ed32b2a46272a19dd111427770c072918aed7a8565010', }, + '@timestamp': new Date().toISOString(), ...overrides, }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts index 27c758ca43a8b..ebe8a34580f76 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts @@ -15,8 +15,8 @@ import { } from '../../routes/metadata/support/test_support'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { - getESQueryHostMetadataByFleetAgentIds, buildUnitedIndexQuery, + getESQueryHostMetadataByFleetAgentIds, } from '../../routes/metadata/query_builders'; import type { HostMetadata } from '../../../../common/endpoint/types'; import type { Agent, PackagePolicy } from '@kbn/fleet-plugin/common'; @@ -137,11 +137,14 @@ describe('EndpointMetadataService', () => { package_policies: packagePolicies, }), ]; + + const newDate = new Date(); const agentPolicyIds = agentPolicies.map((policy) => policy.id); - const endpointMetadataDoc = endpointDocGenerator.generateHostMetadata(); + const endpointMetadataDoc = endpointDocGenerator.generateHostMetadata(newDate.getTime()); const mockAgent = { policy_id: agentPolicies[0].id, policy_revision: agentPolicies[0].revision, + last_checkin: newDate.toISOString(), } as unknown as Agent; const mockDoc = unitedMetadataSearchResponseMock(endpointMetadataDoc, mockAgent); esClient.search.mockResponse(mockDoc); @@ -203,6 +206,7 @@ describe('EndpointMetadataService', () => { revision: packagePolicies[0].revision, }, }, + last_checkin: newDate.toISOString(), }, ], total: 1, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts index 98f42c1a4d8ce..d6247ad1b572b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts @@ -12,7 +12,7 @@ import type { SavedObjectsServiceStart, } from '@kbn/core/server'; -import type { SearchTotalHits, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { SearchResponse, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import type { Agent, AgentPolicy, AgentStatus, PackagePolicy } from '@kbn/fleet-plugin/common'; import type { AgentPolicyServiceInterface, PackagePolicyClient } from '@kbn/fleet-plugin/server'; import { AgentNotFoundError } from '@kbn/fleet-plugin/server'; @@ -32,9 +32,9 @@ import { FleetEndpointPackagePolicyNotFoundError, } from './errors'; import { + buildUnitedIndexQuery, getESQueryHostMetadataByFleetAgentIds, getESQueryHostMetadataByID, - buildUnitedIndexQuery, getESQueryHostMetadataByIDs, } from '../../routes/metadata/query_builders'; import { @@ -176,7 +176,7 @@ export class EndpointMetadataService { } } - // If the agent is not longer active, then that means that the Agent/Endpoint have been un-enrolled from the host + // If the agent is no longer active, then that means that the Agent/Endpoint have been un-enrolled from the host if (fleetAgent && !fleetAgent.active) { throw new EndpointHostUnEnrolledError( `Endpoint with id ${endpointId} (Fleet agent id ${fleetAgentId}) is unenrolled` @@ -251,7 +251,7 @@ export class EndpointMetadataService { } } - // The fleetAgentPolicy might have the endpoint policy in the `package_policies`, lets check that first + // The fleetAgentPolicy might have the endpoint policy in the `package_policies`, let's check that first if ( !endpointPackagePolicy && fleetAgentPolicy && @@ -262,7 +262,7 @@ export class EndpointMetadataService { ); } - // if we still don't have an endpoint package policy, try retrieving it from fleet + // if we still don't have an endpoint package policy, try retrieving it from `fleet` if (!endpointPackagePolicy) { try { endpointPackagePolicy = await this.getFleetEndpointPackagePolicy( @@ -294,6 +294,8 @@ export class EndpointMetadataService { id: endpointPackagePolicy?.id ?? '', }, }, + last_checkin: + _fleetAgent?.last_checkin || new Date(endpointMetadata['@timestamp']).toISOString(), }; } @@ -363,6 +365,8 @@ export class EndpointMetadataService { * * @param esClient * @param queryOptions + * @param soClient + * @param fleetServices * * @throws */ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index 50c6cc8878723..b4a493d7f1312 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -84,6 +84,53 @@ describe('set signal status', () => { status_code: 500, }); }); + + test('calls "esClient.updateByQuery" with queryId when query is defined', async () => { + await server.inject( + getSetSignalStatusByQueryRequest(), + requestContextMock.convertContext(context) + ); + expect(context.core.elasticsearch.client.asCurrentUser.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + query: expect.objectContaining({ + bool: { filter: typicalSetStatusSignalByQueryPayload().query }, + }), + }), + }) + ); + }); + + test('calls "esClient.bulk" with signalIds when ids are defined', async () => { + await server.inject( + getSetSignalStatusByIdsRequest(), + requestContextMock.convertContext(context) + ); + expect(context.core.elasticsearch.client.asCurrentUser.bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + { + update: { + _id: 'somefakeid1', + _index: '.alerts-security.alerts-default', + }, + }, + { + script: expect.anything(), + }, + { + update: { + _id: 'somefakeid2', + _index: '.alerts-security.alerts-default', + }, + }, + { + script: expect.anything(), + }, + ]), + }) + ); + }); }); describe('request validation', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 9681d22d91198..199e0c8f412f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -8,7 +8,7 @@ import { get } from 'lodash'; import { transformError } from '@kbn/securitysolution-es-utils'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; -import type { Logger } from '@kbn/core/server'; +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { setSignalStatusValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/set_signal_status_type_dependents'; import type { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema'; import { setSignalsStatusSchema } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema'; @@ -87,40 +87,20 @@ export const setSignalsStatusRoute = ( } } - let queryObject; - if (signalIds) { - queryObject = { ids: { values: signalIds } }; - } - if (query) { - queryObject = { - bool: { - filter: query, - }, - }; - } try { - const body = await esClient.updateByQuery({ - index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, - conflicts: conflicts ?? 'abort', - // https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html#_refreshing_shards_2 - // Note: Before we tried to use "refresh: wait_for" but I do not think that was available and instead it defaulted to "refresh: true" - // but the tests do not pass with "refresh: false". If at some point a "refresh: wait_for" is implemented, we should use that instead. - refresh: true, - body: { - script: { - source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) { - ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}' - } - if (ctx._source.signal != null && ctx._source.signal.status != null) { - ctx._source.signal.status = '${status}' - }`, - lang: 'painless', - }, - query: queryObject, - }, - ignore_unavailable: true, - }); - return response.ok({ body }); + if (signalIds) { + const body = await updateSignalsStatusByIds(status, signalIds, spaceId, esClient); + return response.ok({ body }); + } else { + const body = await updateSignalsStatusByQuery( + status, + query, + { conflicts: conflicts ?? 'abort' }, + spaceId, + esClient + ); + return response.ok({ body }); + } } catch (err) { // error while getting or updating signal with id: id in signal index .siem-signals const error = transformError(err); @@ -132,3 +112,62 @@ export const setSignalsStatusRoute = ( } ); }; + +const updateSignalsStatusByIds = async ( + status: SetSignalsStatusSchemaDecoded['status'], + signalsId: string[], + spaceId: string, + esClient: ElasticsearchClient +) => + esClient.bulk({ + index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + refresh: 'wait_for', + body: signalsId.flatMap((signalId) => [ + { + update: { _id: signalId, _index: `${DEFAULT_ALERTS_INDEX}-${spaceId}` }, + }, + { + script: getUpdateSignalStatusScript(status), + }, + ]), + }); + +/** + * Please avoid using `updateSignalsStatusByQuery` when possible, use `updateSignalsStatusByIds` instead. + * + * This method calls `updateByQuery` with `refresh: true` which is expensive on serverless. + */ +const updateSignalsStatusByQuery = async ( + status: SetSignalsStatusSchemaDecoded['status'], + query: object | undefined, + options: { conflicts: 'abort' | 'proceed' }, + spaceId: string, + esClient: ElasticsearchClient +) => + esClient.updateByQuery({ + index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + conflicts: options.conflicts, + // https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html#_refreshing_shards_2 + // Note: Before we tried to use "refresh: wait_for" but I do not think that was available and instead it defaulted to "refresh: true" + // but the tests do not pass with "refresh: false". If at some point a "refresh: wait_for" is implemented, we should use that instead. + refresh: true, + body: { + script: getUpdateSignalStatusScript(status), + query: { + bool: { + filter: query, + }, + }, + }, + ignore_unavailable: true, + }); + +const getUpdateSignalStatusScript = (status: SetSignalsStatusSchemaDecoded['status']) => ({ + source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) { + ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}' + } + if (ctx._source.signal != null && ctx._source.signal.status != null) { + ctx._source.signal.status = '${status}' + }`, + lang: 'painless', +}); diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index bb7c6ce5ea4b6..d272cb53f4d80 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -83,7 +83,6 @@ "@kbn/guided-onboarding-plugin", "@kbn/i18n-react", "@kbn/kibana-react-plugin", - "@kbn/core-chrome-browser", "@kbn/ecs-data-quality-dashboard", "@kbn/elastic-assistant", "@kbn/data-views-plugin", diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 23987f7d86e95..70ba2b98d5707 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -11,7 +11,6 @@ import { DefaultNavigation, NavigationKibanaProvider, NavigationTreeDefinition, - getPresets, } from '@kbn/shared-ux-chrome-navigation'; import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -28,7 +27,7 @@ const navigationTree: NavigationTreeDefinition = { breadcrumbStatus: 'hidden', children: [ { - id: 'discover-dashboard-viz', + id: 'discover-dashboard-alerts-slos', children: [ { link: 'discover', @@ -39,44 +38,75 @@ const navigationTree: NavigationTreeDefinition = { }), link: 'dashboards', }, - { - title: i18n.translate('xpack.serverlessObservability.nav.visualizations', { - defaultMessage: 'Visualizations', - }), - link: 'visualize', - }, - ], - }, - { - id: 'alerts-cases-slos', - children: [ { link: 'observability-overview:alerts', }, { - link: 'observability-overview:cases', + link: 'observability-overview:slos', }, { - link: 'observability-overview:slos', + id: 'aiops', + title: 'AIOps', + children: [ + { + title: i18n.translate('xpack.serverlessObservability.nav.ml.jobs', { + defaultMessage: 'Anomaly detection', + }), + link: 'ml:anomalyDetection', + }, + { + title: i18n.translate('xpack.serverlessObservability.ml.spike.analysis', { + defaultMessage: 'Spike analysis', + }), + link: 'ml:explainLogRateSpikes', + icon: 'beaker', + }, + { + link: 'ml:changePointDetections', + icon: 'beaker', + }, + { + title: i18n.translate('xpack.serverlessObservability.nav.ml.job.notifications', { + defaultMessage: 'Job notifications', + }), + link: 'ml:notifications', + }, + ], }, ], }, + { - id: 'apm', - title: 'APM', + id: 'applications', children: [ - { link: 'apm:services' }, { - link: 'apm:traces', + id: 'apm', + title: 'Applications', + children: [ + { + link: 'apm:services', + }, + { + link: 'apm:traces', + }, + { + link: 'apm:dependencies', + }, + ], }, + ], + }, + { + id: 'cases-vis', + children: [ { - title: i18n.translate('xpack.serverlessObservability.nav.logs', { - defaultMessage: 'Logs', - }), - link: 'logs:stream', + link: 'observability-overview:cases', }, { - link: 'apm:dependencies', + title: i18n.translate('xpack.serverlessObservability.nav.visualizations', { + defaultMessage: 'Visualizations', + }), + link: 'visualize', }, ], }, @@ -85,9 +115,8 @@ const navigationTree: NavigationTreeDefinition = { children: [ { title: i18n.translate('xpack.serverlessObservability.nav.getStarted', { - defaultMessage: 'Get started', + defaultMessage: 'Add data', }), - icon: 'launch', link: 'observabilityOnboarding', }, ], @@ -98,7 +127,30 @@ const navigationTree: NavigationTreeDefinition = { footer: [ { type: 'navGroup', - ...getPresets('management'), + id: 'projest_settings_project_nav', + title: 'Project settings', + icon: 'gear', + defaultIsCollapsed: true, + breadcrumbStatus: 'hidden', + children: [ + { + id: 'settings', + children: [ + { + link: 'management', + title: i18n.translate('xpack.serverlessObservability.nav.mngt', { + defaultMessage: 'Management', + }), + }, + { + link: 'integrations', + }, + { + link: 'fleet', + }, + ], + }, + ], }, ], }; diff --git a/x-pack/plugins/serverless_security/common/config.ts b/x-pack/plugins/serverless_security/common/config.ts index 05c1cb4f0b01b..63b173ff3930e 100644 --- a/x-pack/plugins/serverless_security/common/config.ts +++ b/x-pack/plugins/serverless_security/common/config.ts @@ -7,11 +7,18 @@ import { schema, TypeOf } from '@kbn/config-schema'; +export enum ProductLine { + security = 'security', + cloud = 'cloud', + endpoint = 'endpoint', +} + export const productLine = schema.oneOf([ - schema.literal('security'), - schema.literal('endpoint'), - schema.literal('cloud'), + schema.literal(ProductLine.security), + schema.literal(ProductLine.endpoint), + schema.literal(ProductLine.cloud), ]); + export type SecurityProductLine = TypeOf; export const productTier = schema.oneOf([schema.literal('essentials'), schema.literal('complete')]); diff --git a/x-pack/plugins/serverless_security/common/pli/pli_features.test.ts b/x-pack/plugins/serverless_security/common/pli/pli_features.test.ts index 0d46bcc421ec0..c00f8d7fdd9ec 100644 --- a/x-pack/plugins/serverless_security/common/pli/pli_features.test.ts +++ b/x-pack/plugins/serverless_security/common/pli/pli_features.test.ts @@ -6,6 +6,7 @@ */ import { getProductAppFeatures } from './pli_features'; import * as pliConfig from './pli_config'; +import { ProductLine } from '../config'; describe('getProductAppFeatures', () => { it('should return the essentials PLIs features', () => { @@ -18,7 +19,7 @@ describe('getProductAppFeatures', () => { }; const appFeatureKeys = getProductAppFeatures([ - { product_line: 'security', product_tier: 'essentials' }, + { product_line: ProductLine.security, product_tier: 'essentials' }, ]); expect(appFeatureKeys).toEqual(['foo']); @@ -34,7 +35,7 @@ describe('getProductAppFeatures', () => { }; const appFeatureKeys = getProductAppFeatures([ - { product_line: 'security', product_tier: 'complete' }, + { product_line: ProductLine.security, product_tier: 'complete' }, ]); expect(appFeatureKeys).toEqual(['foo', 'baz']); @@ -58,9 +59,9 @@ describe('getProductAppFeatures', () => { }; const appFeatureKeys = getProductAppFeatures([ - { product_line: 'security', product_tier: 'essentials' }, - { product_line: 'endpoint', product_tier: 'complete' }, - { product_line: 'cloud', product_tier: 'essentials' }, + { product_line: ProductLine.security, product_tier: 'essentials' }, + { product_line: ProductLine.endpoint, product_tier: 'complete' }, + { product_line: ProductLine.cloud, product_tier: 'essentials' }, ]); expect(appFeatureKeys).toEqual(['foo', 'bar', 'repeated', 'qux', 'quux', 'corge', 'garply']); diff --git a/x-pack/plugins/serverless_security/kibana.jsonc b/x-pack/plugins/serverless_security/kibana.jsonc index daa7490a38651..306918ed70697 100644 --- a/x-pack/plugins/serverless_security/kibana.jsonc +++ b/x-pack/plugins/serverless_security/kibana.jsonc @@ -14,6 +14,7 @@ ], "requiredPlugins": [ "kibanaReact", + "management", "ml", "security", "securitySolution", diff --git a/x-pack/plugins/serverless_security/public/common/jest.config.js b/x-pack/plugins/serverless_security/public/common/jest.config.js new file mode 100644 index 0000000000000..3ea98b5178343 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/x-pack/plugins/serverless_security/public/common'], + testMatch: [ + '/x-pack/plugins/serverless_security/public/common/**/*.test.{js,mjs,ts,tsx}', + ], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/serverless_security/public/common', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/serverless_security/public/common/**/*.{ts,tsx}', + '!/x-pack/plugins/serverless_security/public/common/*.test.{ts,tsx}', + '!/x-pack/plugins/serverless_security/public/common/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', + '!/x-pack/plugins/serverless_security/public/common/*mock*.{ts,tsx}', + '!/x-pack/plugins/serverless_security/public/common/*.test.{ts,tsx}', + '!/x-pack/plugins/serverless_security/public/common/*.d.ts', + '!/x-pack/plugins/serverless_security/public/common/*.config.ts', + '!/x-pack/plugins/serverless_security/public/common/index.{js,ts,tsx}', + ], +}; diff --git a/x-pack/plugins/serverless_security/public/common/navigation/breadcrumbs.ts b/x-pack/plugins/serverless_security/public/common/navigation/breadcrumbs.ts new file mode 100644 index 0000000000000..d4e9d13b37698 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/breadcrumbs.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Services } from '../services'; + +export const subscribeBreadcrumbs = (services: Services) => { + const { securitySolution, serverless } = services; + securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => { + serverless.setBreadcrumbs(breadcrumbsNav.trailing); + }); +}; diff --git a/x-pack/plugins/serverless_security/public/common/navigation/links/index.ts b/x-pack/plugins/serverless_security/public/common/navigation/links/index.ts new file mode 100644 index 0000000000000..7271c36bbdfdf --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/links/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { getProjectNavLinks$ } from './nav_links'; +export type { ProjectNavLinks, ProjectNavigationLink } from './types'; diff --git a/x-pack/plugins/serverless_security/public/common/navigation/links/nav_links.ts b/x-pack/plugins/serverless_security/public/common/navigation/links/nav_links.ts new file mode 100644 index 0000000000000..289d0a0c55708 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/links/nav_links.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 { map, type Observable } from 'rxjs'; +import type { NavigationLink } from '@kbn/security-solution-plugin/public'; +import type { ProjectNavLinks, ProjectNavigationLink } from './types'; + +export const getProjectNavLinks$ = (navLinks$: Observable): ProjectNavLinks => { + return navLinks$.pipe(map(processNavLinks)); +}; + +// TODO: This is a placeholder function that will be used to process the nav links, +// It will mix internal Security nav links with the external links to other plugins, in the correct order. +const processNavLinks = (navLinks: NavigationLink[]): ProjectNavigationLink[] => navLinks; diff --git a/x-pack/plugins/serverless_security/public/common/navigation/links/types.ts b/x-pack/plugins/serverless_security/public/common/navigation/links/types.ts new file mode 100644 index 0000000000000..47930f64dd6d8 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/links/types.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 type { Observable } from 'rxjs'; +import type { NavigationLink } from '@kbn/security-solution-plugin/public'; + +export interface ProjectNavigationLink extends NavigationLink { + // The appId for external links + appId?: string; +} + +export type ProjectNavLinks = Observable; diff --git a/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.test.ts b/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.test.ts new file mode 100644 index 0000000000000..91d020c332702 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.test.ts @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ChromeNavLink } from '@kbn/core/public'; +import { APP_UI_ID, SecurityPageName } from '@kbn/security-solution-plugin/common'; +import { servicesMocks } from '../services.mock'; +import { subscribeNavigationTree } from './navigation_tree'; +import { BehaviorSubject } from 'rxjs'; +import { mockProjectNavLinks } from '../services.mock'; +import type { ProjectNavigationLink } from './links'; + +const mockChromeNavLinks = jest.fn((): ChromeNavLink[] => []); +const mockChromeGetNavLinks = jest.fn(() => new BehaviorSubject(mockChromeNavLinks())); +const mockChromeNavLinksGet = jest.fn((id: string): ChromeNavLink | undefined => + mockChromeNavLinks().find((link) => link.id === id) +); +const mockChromeNavLinksHas = jest.fn((id: string): boolean => + mockChromeNavLinks().some((link) => link.id === id) +); + +const mockServices = { + ...servicesMocks, + chrome: { + ...servicesMocks.chrome, + navLinks: { + ...servicesMocks.chrome.navLinks, + get: mockChromeNavLinksGet, + has: mockChromeNavLinksHas, + getNavLinks$: mockChromeGetNavLinks, + }, + }, +}; + +const link1Id = 'link-1' as SecurityPageName; +const link2Id = 'link-2' as SecurityPageName; + +const link1: ProjectNavigationLink = { id: link1Id, title: 'link 1' }; +const link2: ProjectNavigationLink = { id: link2Id, title: 'link 2' }; + +const chromeNavLink1: ChromeNavLink = { + id: `${APP_UI_ID}:${link1.id}`, + title: link1.title, + href: '/link1', + url: '/link1', + baseUrl: '', +}; +const chromeNavLink2: ChromeNavLink = { + id: `${APP_UI_ID}:${link2.id}`, + title: link2.title, + href: '/link2', + url: '/link2', + baseUrl: '', +}; + +const waitForDebounce = async () => new Promise((resolve) => setTimeout(resolve, 150)); + +describe('subscribeNavigationTree', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockChromeNavLinks.mockReturnValue([chromeNavLink1, chromeNavLink2]); + }); + + it('should call serverless setNavigation', async () => { + mockProjectNavLinks.mockReturnValueOnce([link1]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLink1.id, + title: link1.title, + path: ['root', chromeNavLink1.id], + deepLink: chromeNavLink1, + }, + ], + }, + ], + }); + }); + + it('should call serverless setNavigation with external link', async () => { + const externalLink = { ...link1, appId: 'externalAppId' }; + const chromeNavLinkExpected = { + ...chromeNavLink1, + id: `${externalLink.appId}:${externalLink.id}`, + }; + mockChromeNavLinks.mockReturnValue([chromeNavLinkExpected]); + mockProjectNavLinks.mockReturnValueOnce([externalLink]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLinkExpected.id, + title: externalLink.title, + path: ['root', chromeNavLinkExpected.id], + deepLink: chromeNavLinkExpected, + }, + ], + }, + ], + }); + }); + + it('should call serverless setNavigation with nested children', async () => { + mockProjectNavLinks.mockReturnValueOnce([{ ...link1, links: [link2] }]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLink1.id, + title: link1.title, + path: ['root', chromeNavLink1.id], + deepLink: chromeNavLink1, + children: [ + { + id: chromeNavLink2.id, + title: link2.title, + path: ['root', chromeNavLink1.id, chromeNavLink2.id], + deepLink: chromeNavLink2, + }, + ], + }, + ], + }, + ], + }); + }); + + it('should not call serverless setNavigation when projectNavLinks is empty', async () => { + mockProjectNavLinks.mockReturnValueOnce([]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).not.toHaveBeenCalled(); + }); + + it('should not call serverless setNavigation when chrome navLinks is empty', async () => { + mockChromeNavLinks.mockReturnValue([]); + mockProjectNavLinks.mockReturnValueOnce([link1]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).not.toHaveBeenCalled(); + }); + + it('should debounce updates', async () => { + const id = 'expectedId' as SecurityPageName; + const linkExpected = { ...link1, id }; + const chromeNavLinkExpected = { ...chromeNavLink1, id: `${APP_UI_ID}:${id}` }; + + const chromeGetNavLinks$ = new BehaviorSubject([chromeNavLink1]); + mockChromeGetNavLinks.mockReturnValue(chromeGetNavLinks$); + + mockChromeNavLinks.mockReturnValue([chromeNavLink1, chromeNavLink2, chromeNavLinkExpected]); + mockProjectNavLinks.mockReturnValueOnce([linkExpected]); + + subscribeNavigationTree(mockServices); + + chromeGetNavLinks$.next([chromeNavLink1]); + chromeGetNavLinks$.next([chromeNavLink2]); + chromeGetNavLinks$.next([chromeNavLinkExpected]); + + expect(mockServices.serverless.setNavigation).not.toHaveBeenCalled(); + + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledTimes(1); + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLinkExpected.id, + title: link1.title, + path: ['root', chromeNavLinkExpected.id], + deepLink: chromeNavLinkExpected, + }, + ], + }, + ], + }); + }); + + it('should not include links that are not in the chrome navLinks', async () => { + mockChromeNavLinks.mockReturnValue([chromeNavLink2]); + mockProjectNavLinks.mockReturnValueOnce([link1, link2]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLink2.id, + title: link2.title, + path: ['root', chromeNavLink2.id], + deepLink: chromeNavLink2, + }, + ], + }, + ], + }); + }); + + it('should set hidden breadcrumb for blacklisted links', async () => { + const chromeNavLinkTest = { + ...chromeNavLink1, + id: `${APP_UI_ID}:${SecurityPageName.usersEvents}`, // userEvents link is blacklisted + }; + mockChromeNavLinks.mockReturnValue([chromeNavLinkTest, chromeNavLink2]); + mockProjectNavLinks.mockReturnValueOnce([ + { ...link1, id: SecurityPageName.usersEvents }, + link2, + ]); + + subscribeNavigationTree(mockServices); + await waitForDebounce(); + + expect(mockServices.serverless.setNavigation).toHaveBeenCalledWith({ + navigationTree: [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: [ + { + id: chromeNavLinkTest.id, + title: link1.title, + path: ['root', chromeNavLinkTest.id], + deepLink: chromeNavLinkTest, + breadcrumbStatus: 'hidden', + }, + { + id: chromeNavLink2.id, + title: link2.title, + path: ['root', chromeNavLink2.id], + deepLink: chromeNavLink2, + }, + ], + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.ts b/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.ts new file mode 100644 index 0000000000000..70464c0c53f5d --- /dev/null +++ b/x-pack/plugins/serverless_security/public/common/navigation/navigation_tree.ts @@ -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 { ChromeNavLinks, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import { APP_UI_ID, SecurityPageName } from '@kbn/security-solution-plugin/common'; +import { combineLatest, skipWhile, debounceTime } from 'rxjs'; +import type { Services } from '../services'; +import type { ProjectNavigationLink } from './links/types'; + +// We need to hide breadcrumbs for some pages (tabs) because they appear duplicated. +// These breadcrumbs are incorrectly processed as trailing breadcrumbs in SecuritySolution, because of `SpyRoute` architecture limitations. +// They are navLinks tree with a SecurityPageName, so they should be treated as leading breadcrumbs in ESS as well. +// TODO: Improve the breadcrumbs logic in `use_breadcrumbs_nav` to avoid this workaround. +const HIDDEN_BREADCRUMBS = new Set([ + SecurityPageName.networkDns, + SecurityPageName.networkHttp, + SecurityPageName.networkTls, + SecurityPageName.networkAnomalies, + SecurityPageName.networkEvents, + SecurityPageName.usersAuthentications, + SecurityPageName.usersAnomalies, + SecurityPageName.usersRisk, + SecurityPageName.usersEvents, + SecurityPageName.uncommonProcesses, + SecurityPageName.hostsAnomalies, + SecurityPageName.hostsEvents, + SecurityPageName.hostsRisk, + SecurityPageName.sessions, +]); + +export const subscribeNavigationTree = (services: Services): void => { + const { chrome, serverless, getProjectNavLinks$ } = services; + + combineLatest([ + getProjectNavLinks$().pipe(skipWhile((navLink) => navLink.length === 0)), + chrome.navLinks.getNavLinks$().pipe(skipWhile((chromeNavLinks) => chromeNavLinks.length === 0)), + ]) + .pipe(debounceTime(100)) // avoid multiple calls in a short time + .subscribe(([projectNavLinks]) => { + // The root link is temporary until the issue about having multiple links at first level is solved. + // TODO: Assign the navigationTree nodes when the issue is solved: + // const navigationTree = formatChromeProjectNavNodes(chrome.navLinks, projectNavLinks), + const navigationTree: ChromeProjectNavigationNode[] = [ + { + id: 'root', + title: 'Root', + path: ['root'], + breadcrumbStatus: 'hidden', + children: formatChromeProjectNavNodes(chrome.navLinks, projectNavLinks, ['root']), + }, + ]; + serverless.setNavigation({ navigationTree }); + }); +}; + +const formatChromeProjectNavNodes = ( + chromeNavLinks: ChromeNavLinks, + projectNavLinks: ProjectNavigationLink[], + path: string[] = [] +): ChromeProjectNavigationNode[] => + projectNavLinks.reduce((navNodes, navLink) => { + const { id: deepLinkId, appId = APP_UI_ID, links, title } = navLink; + + const id = deepLinkId ? `${appId}:${deepLinkId}` : appId; + + if (chromeNavLinks.has(id)) { + const breadcrumbHidden = appId === APP_UI_ID && HIDDEN_BREADCRUMBS.has(deepLinkId); + const link: ChromeProjectNavigationNode = { + id, + title, + path: [...path, id], + deepLink: chromeNavLinks.get(id), + ...(breadcrumbHidden && { breadcrumbStatus: 'hidden' }), + }; + + if (links?.length) { + link.children = formatChromeProjectNavNodes(chromeNavLinks, links, link.path); + } + navNodes.push(link); + } + return navNodes; + }, []); diff --git a/x-pack/plugins/serverless_security/public/services.mock.tsx b/x-pack/plugins/serverless_security/public/common/services.mock.tsx similarity index 68% rename from x-pack/plugins/serverless_security/public/services.mock.tsx rename to x-pack/plugins/serverless_security/public/common/services.mock.tsx index 142ebb2152e63..a3aba806af18c 100644 --- a/x-pack/plugins/serverless_security/public/services.mock.tsx +++ b/x-pack/plugins/serverless_security/public/common/services.mock.tsx @@ -11,12 +11,20 @@ import { coreMock } from '@kbn/core/public/mocks'; import { serverlessMock } from '@kbn/serverless/public/mocks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import { securitySolutionMock } from '@kbn/security-solution-plugin/public/mocks'; +import { BehaviorSubject } from 'rxjs'; +import { managementPluginMock } from '@kbn/management-plugin/public/mocks'; +import type { ProjectNavigationLink } from './navigation/links'; +import type { Services } from './services'; -export const servicesMocks = { +export const mockProjectNavLinks = jest.fn((): ProjectNavigationLink[] => []); + +export const servicesMocks: Services = { ...coreMock.createStart(), serverless: serverlessMock.createStart(), security: securityMock.createStart(), securitySolution: securitySolutionMock.createStart(), + getProjectNavLinks$: jest.fn(() => new BehaviorSubject(mockProjectNavLinks())), + management: managementPluginMock.createStartContract(), }; export const KibanaServicesProvider = React.memo(({ children }) => ( diff --git a/x-pack/plugins/serverless_security/public/services.tsx b/x-pack/plugins/serverless_security/public/common/services.tsx similarity index 56% rename from x-pack/plugins/serverless_security/public/services.tsx rename to x-pack/plugins/serverless_security/public/common/services.tsx index b62b91d1bbfea..f3bfe1dbbfa1b 100644 --- a/x-pack/plugins/serverless_security/public/services.tsx +++ b/x-pack/plugins/serverless_security/public/common/services.tsx @@ -12,16 +12,27 @@ import { useKibana as useKibanaReact, } from '@kbn/kibana-react-plugin/public'; -import type { ServerlessSecurityPluginStartDependencies } from './types'; +import type { ServerlessSecurityPluginStartDependencies } from '../types'; +import { getProjectNavLinks$, type ProjectNavLinks } from './navigation/links'; -export type Services = CoreStart & ServerlessSecurityPluginStartDependencies; +interface InternalServices { + getProjectNavLinks$: () => ProjectNavLinks; +} +export type Services = CoreStart & ServerlessSecurityPluginStartDependencies & InternalServices; export const KibanaServicesProvider: React.FC<{ - core: CoreStart; - pluginsStart: ServerlessSecurityPluginStartDependencies; -}> = ({ core, pluginsStart, children }) => { - const services: Services = { ...core, ...pluginsStart }; + services: Services; +}> = ({ services, children }) => { return {children}; }; export const useKibana = () => useKibanaReact(); + +export const createServices = ( + core: CoreStart, + pluginsStart: ServerlessSecurityPluginStartDependencies +): Services => { + const { securitySolution } = pluginsStart; + const projectNavLinks$ = getProjectNavLinks$(securitySolution.getNavLinks$()); + return { ...core, ...pluginsStart, getProjectNavLinks$: () => projectNavLinks$ }; +}; diff --git a/x-pack/plugins/serverless_security/public/components/get_started/get_started.test.tsx b/x-pack/plugins/serverless_security/public/components/get_started/get_started.test.tsx index a81d2525f6a84..ddf0b17985223 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/get_started.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/get_started.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { GetStartedComponent } from './get_started'; +import { SecurityProductTypes } from '../../../common/config'; jest.mock('./toggle_panel'); jest.mock('./welcome_panel'); @@ -20,9 +21,15 @@ jest.mock('@elastic/eui', () => { }; }); +const productTypes = [ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'complete' }, + { product_line: 'cloud', product_tier: 'complete' }, +] as SecurityProductTypes; + describe('GetStartedComponent', () => { it('should render page title, subtitle, and description', () => { - const { getByText } = render(); + const { getByText } = render(); const pageTitle = getByText('Welcome'); const subtitle = getByText('Let’s get started'); @@ -35,8 +42,16 @@ describe('GetStartedComponent', () => { expect(description).toBeInTheDocument(); }); + it('should render Product Switch', () => { + const { getByTestId } = render(); + + const productSwitch = getByTestId('product-switch'); + + expect(productSwitch).toBeInTheDocument(); + }); + it('should render WelcomePanel and TogglePanel', () => { - const { getByTestId } = render(); + const { getByTestId } = render(); const welcomePanel = getByTestId('welcome-panel'); const togglePanel = getByTestId('toggle-panel'); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/get_started.tsx b/x-pack/plugins/serverless_security/public/components/get_started/get_started.tsx index 1532528b6ff53..b29ba3b30d6a4 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/get_started.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/get_started.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiTitle, useEuiTheme } from '@elastic/eui'; +import { EuiTitle, useEuiTheme, useEuiShadow } from '@elastic/eui'; import React from 'react'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { css } from '@emotion/react'; @@ -17,10 +17,22 @@ import { GET_STARTED_PAGE_SUBTITLE, GET_STARTED_PAGE_TITLE, } from './translations'; +import { SecurityProductTypes } from '../../../common/config'; +import { ProductSwitch } from './product_switch'; +import { useTogglePanel } from './use_toggle_panel'; -export const GetStartedComponent: React.FC = () => { - const { euiTheme } = useEuiTheme(); +const CONTENT_WIDTH = 1150; +export const GetStartedComponent: React.FC<{ productTypes: SecurityProductTypes }> = ({ + productTypes, +}) => { + const { euiTheme } = useEuiTheme(); + const shadow = useEuiShadow('s'); + const { + onProductSwitchChanged, + onStepClicked, + state: { activeProducts, activeCards, finishedSteps }, + } = useTogglePanel({ productTypes }); return ( { `} > { > + + + - + ); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/helpers.test.ts b/x-pack/plugins/serverless_security/public/components/get_started/helpers.test.ts index 0a4e32834bc94..f5511d4eaa85d 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/helpers.test.ts +++ b/x-pack/plugins/serverless_security/public/components/get_started/helpers.test.ts @@ -13,18 +13,18 @@ import { updateCard, } from './helpers'; import { - ActiveCard, + ActiveCards, Card, CardId, GetMoreFromElasticSecurityCardId, GetSetUpCardId, IntroductionSteps, - ProductId, Section, SectionId, StepId, } from './types'; import * as sectionsConfigs from './sections'; +import { ProductLine } from '../../../common/config'; const mockSections = jest.spyOn(sectionsConfigs, 'getSections'); describe('getCardTimeInMinutes', () => { it('should calculate the total time in minutes for a card correctly', () => { @@ -74,8 +74,8 @@ describe('getCardStepsLeft', () => { describe('isCardActive', () => { it('should return true if the card is active based on the active products', () => { - const card = { productTypeRequired: [ProductId.analytics, ProductId.cloud] } as Card; - const activeProducts = new Set([ProductId.analytics]); + const card = { productLineRequired: [ProductLine.security, ProductLine.cloud] } as Card; + const activeProducts = new Set([ProductLine.security]); const isActive = isCardActive(card, activeProducts); @@ -84,7 +84,7 @@ describe('isCardActive', () => { it('should return true if the card has no product type requirement', () => { const card = {} as Card; - const activeProducts = new Set([ProductId.analytics]); + const activeProducts = new Set([ProductLine.security]); const isActive = isCardActive(card, activeProducts); @@ -92,8 +92,8 @@ describe('isCardActive', () => { }); it('should return false if the card is not active based on the active products', () => { - const card = { productTypeRequired: [ProductId.analytics, ProductId.cloud] } as Card; - const activeProducts = new Set([ProductId.endpoint]); + const card = { productLineRequired: [ProductLine.security, ProductLine.cloud] } as Card; + const activeProducts = new Set([ProductLine.endpoint]); const isActive = isCardActive(card, activeProducts); @@ -140,7 +140,7 @@ describe('setupCards', () => { }; it('should set up active cards based on active products', () => { const finishedSteps = {} as unknown as Record>; - const activeProducts = new Set([ProductId.cloud]); + const activeProducts = new Set([ProductLine.cloud]); const activeCards = setupCards(finishedSteps, activeProducts); @@ -148,8 +148,8 @@ describe('setupCards', () => { ...analyticProductActiveCards, [SectionId.getSetUp]: { ...analyticProductActiveCards[SectionId.getSetUp], - [GetSetUpCardId.protectYourEnvironmentInRuntime]: { - id: GetSetUpCardId.protectYourEnvironmentInRuntime, + [GetSetUpCardId.protectYourEnvironmentInRealtime]: { + id: GetSetUpCardId.protectYourEnvironmentInRealtime, timeInMins: 0, stepsLeft: 0, }, @@ -159,7 +159,7 @@ describe('setupCards', () => { it('should skip inactive cards based on finished steps and active products', () => { const finishedSteps = {} as Record>; - const activeProducts = new Set([ProductId.analytics]); + const activeProducts = new Set([ProductLine.security]); const activeCards = setupCards(finishedSteps, activeProducts); @@ -171,7 +171,7 @@ describe('setupCards', () => { [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), } as unknown as Record>; - const activeProducts: Set = new Set(); + const activeProducts: Set = new Set(); const activeCards = setupCards(finishedSteps, activeProducts); @@ -188,7 +188,7 @@ describe('setupCards', () => { const finishedSteps = { [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), } as unknown as Record>; - const activeProducts = new Set([ProductId.analytics]); + const activeProducts = new Set([ProductLine.security]); const activeCards = setupCards(finishedSteps, activeProducts); @@ -202,7 +202,7 @@ describe('updateCard', () => { const finishedSteps = { [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), } as unknown as Record>; - const activeProducts = new Set([ProductId.analytics, ProductId.cloud]); + const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); const activeCards = { [SectionId.getSetUp]: { @@ -221,8 +221,8 @@ describe('updateCard', () => { timeInMins: 0, stepsLeft: 0, }, - [GetSetUpCardId.protectYourEnvironmentInRuntime]: { - id: GetSetUpCardId.protectYourEnvironmentInRuntime, + [GetSetUpCardId.protectYourEnvironmentInRealtime]: { + id: GetSetUpCardId.protectYourEnvironmentInRealtime, timeInMins: 0, stepsLeft: 0, }, @@ -244,7 +244,7 @@ describe('updateCard', () => { timeInMins: 0, }, }, - } as Record>; + } as ActiveCards; it('should update the active card based on finished steps and active products', () => { const sectionId = SectionId.getSetUp; @@ -273,7 +273,7 @@ describe('updateCard', () => { it('should return null if the card is inactive based on active products', () => { const sectionId = SectionId.getSetUp; - const cardId = GetSetUpCardId.protectYourEnvironmentInRuntime; + const cardId = GetSetUpCardId.protectYourEnvironmentInRealtime; const updatedCards = updateCard({ finishedSteps, diff --git a/x-pack/plugins/serverless_security/public/components/get_started/helpers.ts b/x-pack/plugins/serverless_security/public/components/get_started/helpers.ts index 1f4380e5afae6..0042550a34c4b 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/helpers.ts +++ b/x-pack/plugins/serverless_security/public/components/get_started/helpers.ts @@ -5,8 +5,9 @@ * 2.0. */ +import { ProductLine } from '../../../common/config'; import { getSections } from './sections'; -import { ActiveCard, Card, CardId, ProductId, SectionId, StepId } from './types'; +import { ActiveCard, ActiveCards, Card, CardId, SectionId, StepId } from './types'; export const getCardTimeInMinutes = (card: Card, stepsDone: Set) => card.steps?.reduce( @@ -18,13 +19,13 @@ export const getCardTimeInMinutes = (card: Card, stepsDone: Set) => export const getCardStepsLeft = (card: Card, stepsDone: Set) => (card.steps?.length ?? 0) - (stepsDone.size ?? 0); -export const isCardActive = (card: Card, activeProducts: Set) => - !card.productTypeRequired || - card.productTypeRequired?.some((condition) => activeProducts.has(condition)); +export const isCardActive = (card: Card, activeProducts: Set) => + !card.productLineRequired || + card.productLineRequired?.some((condition) => activeProducts.has(condition)); export const setupCards = ( finishedSteps: Record>, - activeProducts: Set + activeProducts: Set ) => activeProducts.size > 0 ? getSections().reduce((acc, section) => { @@ -46,7 +47,7 @@ export const setupCards = ( acc[section.id] = cardsInSections; } return acc; - }, {} as Record>) + }, {} as ActiveCards) : null; export const updateCard = ({ @@ -57,11 +58,11 @@ export const updateCard = ({ cardId, }: { finishedSteps: Record>; - activeProducts: Set; - activeCards: Record> | null; + activeProducts: Set; + activeCards: ActiveCards | null; sectionId: SectionId; cardId: CardId; -}): Record> | null => { +}): ActiveCards | null => { const sections = getSections(); const section = sections.find(({ id }) => id === sectionId); const cards = section?.cards; diff --git a/x-pack/plugins/serverless_security/public/components/get_started/index.tsx b/x-pack/plugins/serverless_security/public/components/get_started/index.tsx index 88227330bfde9..0ee8c7a1ce62d 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/index.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/index.tsx @@ -7,20 +7,18 @@ import React from 'react'; -import { CoreStart } from '@kbn/core/public'; - +import { KibanaServicesProvider, type Services } from '../../common/services'; import type { GetStartedComponent } from './types'; import { GetStarted } from './lazy'; -import { KibanaServicesProvider } from '../../services'; -import { ServerlessSecurityPluginStartDependencies } from '../../types'; +import { SecurityProductTypes } from '../../../common/config'; export const getSecurityGetStartedComponent = ( - core: CoreStart, - pluginsStart: ServerlessSecurityPluginStartDependencies + services: Services, + productTypes: SecurityProductTypes ): GetStartedComponent => { return () => ( - - + + ); }; diff --git a/x-pack/plugins/serverless_security/public/components/get_started/lazy.tsx b/x-pack/plugins/serverless_security/public/components/get_started/lazy.tsx index f968ad5d2ab22..3130bbe272f52 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/lazy.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/lazy.tsx @@ -6,13 +6,14 @@ */ import React, { lazy, Suspense } from 'react'; import { EuiLoadingLogo } from '@elastic/eui'; +import { SecurityProductTypes } from '../../../common/config'; const GetStartedLazy = lazy(() => import('./get_started')); const centerLogoStyle = { display: 'flex', margin: 'auto' }; -export const GetStarted = () => ( +export const GetStarted = ({ productTypes }: { productTypes: SecurityProductTypes }) => ( }> - + ); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/product_switch.test.tsx b/x-pack/plugins/serverless_security/public/components/get_started/product_switch.test.tsx index 49c0087f59830..29c01ddce5376 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/product_switch.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/product_switch.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import { ProductSwitch } from './product_switch'; import { EuiThemeComputed } from '@elastic/eui'; -import { ProductId } from './types'; +import { ProductLine } from '../../../common/config'; describe('ProductSwitch', () => { const onProductSwitchChangedMock = jest.fn(); @@ -49,12 +49,12 @@ describe('ProductSwitch', () => { fireEvent.click(analyticsSwitch); expect(onProductSwitchChangedMock).toHaveBeenCalledWith( - expect.objectContaining({ id: 'analytics' }) + expect.objectContaining({ id: 'security' }) ); }); it('should have checked switches for activeProducts', () => { - const activeProducts = new Set([ProductId.analytics, ProductId.endpoint]); + const activeProducts = new Set([ProductLine.security, ProductLine.endpoint]); const { getByTestId } = render( { /> ); - const analyticsSwitch = getByTestId('analytics'); + const analyticsSwitch = getByTestId('security'); const cloudSwitch = getByTestId('cloud'); const endpointSwitch = getByTestId('endpoint'); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/product_switch.tsx b/x-pack/plugins/serverless_security/public/components/get_started/product_switch.tsx index 910618c1a3f4c..822e5f3e69ca0 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/product_switch.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/product_switch.tsx @@ -8,30 +8,30 @@ import { EuiPanel, EuiSwitch, EuiText, EuiThemeComputed, EuiTitle } from '@elastic/eui'; import { css } from '@emotion/react'; import React, { useMemo } from 'react'; +import { ProductLine } from '../../../common/config'; import * as i18n from './translations'; -import { ProductId, Switch } from './types'; +import { Switch } from './types'; const switches: Switch[] = [ { - id: ProductId.analytics, + id: ProductLine.security, label: i18n.ANALYTICS_SWITCH_LABEL, }, { - id: ProductId.cloud, + id: ProductLine.cloud, label: i18n.CLOUD_SWITCH_LABEL, }, { - id: ProductId.endpoint, + id: ProductLine.endpoint, label: i18n.ENDPOINT_SWITCH_LABEL, }, ]; const ProductSwitchComponent: React.FC<{ onProductSwitchChanged: (item: Switch) => void; - activeProducts: Set; - shadow?: string; + activeProducts: Set; euiTheme: EuiThemeComputed; -}> = ({ onProductSwitchChanged, activeProducts, euiTheme, shadow = '' }) => { +}> = ({ onProductSwitchChanged, activeProducts, euiTheme }) => { const switchNodes = useMemo( () => switches.map((item) => ( @@ -58,8 +58,7 @@ const ProductSwitchComponent: React.FC<{ paddingSize="none" hasShadow={false} css={css` - padding: ${euiTheme.base * 1.25}px ${euiTheme.base * 2.25}px; - ${shadow}; + padding: ${euiTheme.base * 1.25}px 0; `} borderRadius="none" > diff --git a/x-pack/plugins/serverless_security/public/components/get_started/reducer.test.ts b/x-pack/plugins/serverless_security/public/components/get_started/reducer.test.ts index 970b7ac6dbef0..259923080bfaa 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/reducer.test.ts +++ b/x-pack/plugins/serverless_security/public/components/get_started/reducer.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ProductLine } from '../../../common/config'; import { reducer, getFinishedStepsInitialStates, @@ -12,12 +13,11 @@ import { getActiveCardsInitialStates, } from './reducer'; import { - ActiveCard, + ActiveCards, CardId, GetSetUpCardId, GetStartedPageActions, IntroductionSteps, - ProductId, SectionId, StepId, ToggleProductAction, @@ -28,25 +28,25 @@ import { describe('reducer', () => { it('should toggle section correctly', () => { const initialState = { - activeProducts: new Set([ProductId.analytics]), + activeProducts: new Set([ProductLine.security]), finishedSteps: {} as Record>, - activeCards: {} as Record> | null, + activeCards: {} as ActiveCards | null, }; const action: ToggleProductAction = { type: GetStartedPageActions.ToggleProduct, - payload: { section: ProductId.analytics }, + payload: { section: ProductLine.security }, }; const nextState = reducer(initialState, action); - expect(nextState.activeProducts.has(ProductId.analytics)).toBe(false); + expect(nextState.activeProducts.has(ProductLine.security)).toBe(false); expect(nextState.activeCards).toBeNull(); }); it('should add a finished step correctly', () => { const initialState = { - activeProducts: new Set([ProductId.analytics]), + activeProducts: new Set([ProductLine.security]), finishedSteps: {} as Record>, activeCards: { getSetUp: { @@ -56,7 +56,7 @@ describe('reducer', () => { timeInMins: 3, }, }, - } as unknown as Record> | null, + } as unknown as ActiveCards | null, }; const action: AddFinishedStepAction = { @@ -103,17 +103,17 @@ describe('getFinishedStepsInitialStates', () => { describe('getActiveSectionsInitialStates', () => { it('should return the initial states of active sections correctly', () => { - const activeProducts = [ProductId.analytics]; + const activeProducts = [ProductLine.security]; const initialStates = getActiveSectionsInitialStates({ activeProducts }); - expect(initialStates.has(ProductId.analytics)).toBe(true); + expect(initialStates.has(ProductLine.security)).toBe(true); }); }); describe('getActiveCardsInitialStates', () => { it('should return the initial states of active cards correctly', () => { - const activeProducts = new Set([ProductId.analytics]); + const activeProducts = new Set([ProductLine.security]); const finishedSteps = { [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), } as unknown as Record>; diff --git a/x-pack/plugins/serverless_security/public/components/get_started/reducer.tsx b/x-pack/plugins/serverless_security/public/components/get_started/reducer.tsx index 2164bbee177bd..403bfb85e0a93 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/reducer.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/reducer.tsx @@ -5,11 +5,11 @@ * 2.0. */ +import { ProductLine } from '../../../common/config'; import { setupCards, updateCard } from './helpers'; import { CardId, GetStartedPageActions, - ProductId, StepId, ToggleProductAction, TogglePanelReducer, @@ -80,13 +80,13 @@ export const getFinishedStepsInitialStates = ({ export const getActiveSectionsInitialStates = ({ activeProducts, }: { - activeProducts: ProductId[]; + activeProducts: ProductLine[]; }) => new Set(activeProducts); export const getActiveCardsInitialStates = ({ activeProducts, finishedSteps, }: { - activeProducts: Set; + activeProducts: Set; finishedSteps: Record>; }) => setupCards(finishedSteps, activeProducts); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/sections.tsx b/x-pack/plugins/serverless_security/public/components/get_started/sections.tsx index 4a03eaff2dd76..33dbb6f62aa40 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/sections.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/sections.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { Section, - ProductId, SectionId, GetMoreFromElasticSecurityCardId, GetSetUpCardId, @@ -17,12 +16,7 @@ import { import * as i18n from './translations'; import respond from './images/respond.svg'; import protect from './images/protect.svg'; - -export const ActiveConditions = { - analyticsToggled: [ProductId.analytics], - cloudToggled: [ProductId.cloud], - endpointToggled: [ProductId.endpoint], -}; +import { ProductLine } from '../../../common/config'; export const introductionSteps = [ { @@ -85,11 +79,8 @@ export const sections: Section[] = [ { icon: { type: protect, size: 'xl' }, title: i18n.PROTECT_YOUR_ENVIRONMENT_TITLE, - id: GetSetUpCardId.protectYourEnvironmentInRuntime, - productTypeRequired: [ - ...ActiveConditions.cloudToggled, - ...ActiveConditions.endpointToggled, - ], + id: GetSetUpCardId.protectYourEnvironmentInRealtime, + productLineRequired: [ProductLine.cloud, ProductLine.endpoint], }, ], }, diff --git a/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.test.tsx b/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.test.tsx index a8350826ed6da..1a924b70fc45f 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.test.tsx @@ -5,10 +5,11 @@ * 2.0. */ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { TogglePanel } from './toggle_panel'; -import { getStartedStorage as mockGetStartedStorage } from '../../lib/get_started/storage'; import { useSetUpCardSections } from './use_setup_cards'; +import { ActiveCards, CardId, GetSetUpCardId, IntroductionSteps, SectionId, StepId } from './types'; +import { ProductLine } from '../../../common/config'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), @@ -16,37 +17,63 @@ jest.mock('@elastic/eui', () => ({ useEuiShadow: jest.fn(), })); -jest.mock('../../services', () => ({ - useKibana: jest.fn(() => ({ - services: { - storage: {}, - }, - })), -})); - jest.mock('../../lib/get_started/storage'); jest.mock('./use_setup_cards', () => ({ useSetUpCardSections: jest.fn(), })); +const finishedSteps = { + [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), +} as unknown as Record>; +const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); + +const activeCards = { + [SectionId.getSetUp]: { + [GetSetUpCardId.introduction]: { + id: GetSetUpCardId.introduction, + timeInMins: 3, + stepsLeft: 1, + }, + [GetSetUpCardId.bringInYourData]: { + id: GetSetUpCardId.bringInYourData, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.activateAndCreateRules]: { + id: GetSetUpCardId.activateAndCreateRules, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.protectYourEnvironmentInRealtime]: { + id: GetSetUpCardId.protectYourEnvironmentInRealtime, + timeInMins: 0, + stepsLeft: 0, + }, + }, +} as ActiveCards; + describe('TogglePanel', () => { - const mockUseSetUpCardSections = { setUpSections: jest.fn(() => null) }; + const mockUseSetUpCardSections = { + setUpSections: jest.fn(() =>
), + }; + const onStepClicked = jest.fn(); beforeEach(() => { jest.clearAllMocks(); (useSetUpCardSections as jest.Mock).mockReturnValue(mockUseSetUpCardSections); }); - it('should render the product switch ', () => { - const { getByTestId } = render(); - - expect(getByTestId('product-switch')).toBeInTheDocument(); - }); - it('should render empty prompt', () => { - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText(`Hmm, there doesn't seem to be anything there`)).toBeInTheDocument(); expect( @@ -54,16 +81,16 @@ describe('TogglePanel', () => { ).toBeInTheDocument(); }); - it('should toggle active sections when a product switch is changed', () => { - const { getByText } = render(); - - const analyticsSwitch = getByText('Analytics'); - const cloudSwitch = getByText('Cloud'); - - fireEvent.click(analyticsSwitch); - expect(mockGetStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith('analytics'); + it('should render sections', () => { + const { getByTestId } = render( + + ); - fireEvent.click(cloudSwitch); - expect(mockGetStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith('cloud'); + expect(getByTestId(`mock-sections`)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.tsx b/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.tsx index 3ce851fc8a232..af365b83bd80c 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/toggle_panel.tsx @@ -5,98 +5,46 @@ * 2.0. */ -import React, { useCallback, useMemo, useReducer } from 'react'; +import React from 'react'; import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, useEuiShadow, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -import { Switch, GetStartedPageActions, StepId, CardId, SectionId } from './types'; import * as i18n from './translations'; -import { ProductSwitch } from './product_switch'; import { useSetUpCardSections } from './use_setup_cards'; -import { getStartedStorage } from '../../lib/get_started/storage'; -import { - getActiveCardsInitialStates, - getActiveSectionsInitialStates, - getFinishedStepsInitialStates, - reducer, -} from './reducer'; -const TogglePanelComponent = () => { +import { ActiveCards, CardId, IntroductionSteps, SectionId } from './types'; +import { ProductLine } from '../../../common/config'; + +const TogglePanelComponent: React.FC<{ + finishedSteps: Record>; + activeCards: ActiveCards | null; + activeProducts: Set; + onStepClicked: ({ + stepId, + cardId, + sectionId, + }: { + stepId: IntroductionSteps; + cardId: CardId; + sectionId: SectionId; + }) => void; +}> = ({ finishedSteps, activeCards, activeProducts, onStepClicked }) => { const { euiTheme } = useEuiTheme(); const shadow = useEuiShadow('s'); - const { - getAllFinishedStepsFromStorage, - getActiveProductsFromStorage, - toggleActiveProductsInStorage, - addFinishedStepToStorage, - } = getStartedStorage; - const finishedStepsInitialStates = useMemo( - () => getFinishedStepsInitialStates({ finishedSteps: getAllFinishedStepsFromStorage() }), - [getAllFinishedStepsFromStorage] - ); - - const activeSectionsInitialStates = useMemo( - () => getActiveSectionsInitialStates({ activeProducts: getActiveProductsFromStorage() }), - [getActiveProductsFromStorage] - ); - - const activeCardsInitialStates = useMemo( - () => - getActiveCardsInitialStates({ - activeProducts: activeSectionsInitialStates, - finishedSteps: finishedStepsInitialStates, - }), - [activeSectionsInitialStates, finishedStepsInitialStates] - ); - const [state, dispatch] = useReducer(reducer, { - activeProducts: activeSectionsInitialStates, - finishedSteps: finishedStepsInitialStates, - activeCards: activeCardsInitialStates, - }); const { setUpSections } = useSetUpCardSections({ euiTheme, shadow }); - const onStepClicked = useCallback( - ({ stepId, cardId, sectionId }: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => { - dispatch({ - type: GetStartedPageActions.AddFinishedStep, - payload: { stepId, cardId, sectionId }, - }); - addFinishedStepToStorage(cardId, stepId); - }, - [addFinishedStepToStorage] - ); const sectionNodes = setUpSections({ onStepClicked, - finishedSteps: state.finishedSteps, - activeCards: state.activeCards, + finishedSteps, + activeCards, }); - const onProductSwitchChanged = useCallback( - (section: Switch) => { - dispatch({ type: GetStartedPageActions.ToggleProduct, payload: { section: section.id } }); - toggleActiveProductsInStorage(section.id); - }, - [toggleActiveProductsInStorage] - ); return ( - - - - - {state.activeProducts.size > 0 ? ( + + {activeProducts.size > 0 ? ( sectionNodes ) : ( >; export enum SectionId { getSetUp = 'getSetUp', @@ -71,7 +68,7 @@ export enum GetSetUpCardId { activateAndCreateRules = 'activateAndCreateRules', bringInYourData = 'bringInYourData', introduction = 'introduction', - protectYourEnvironmentInRuntime = 'protectYourEnvironmentInRuntime', + protectYourEnvironmentInRealtime = 'protectYourEnvironmentInRealtime', } export enum IntroductionSteps { @@ -90,14 +87,14 @@ export interface ActiveCard { stepsLeft: number; } export interface TogglePanelReducer { - activeProducts: Set; + activeProducts: Set; finishedSteps: Record>; activeCards: Record> | null; } export interface ToggleProductAction { type: GetStartedPageActions.ToggleProduct; - payload: { section: ProductId }; + payload: { section: ProductLine }; } export interface AddFinishedStepAction { @@ -106,7 +103,7 @@ export interface AddFinishedStepAction { } export interface Switch { - id: ProductId; + id: ProductLine; label: string; } diff --git a/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.test.tsx b/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.test.tsx index dcf8c6a3cfb38..6e726fd9a002f 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.test.tsx @@ -9,7 +9,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { EuiThemeComputed } from '@elastic/eui'; import { useSetUpCardSections } from './use_setup_cards'; import { - ActiveCard, + ActiveCards, CardId, GetMoreFromElasticSecurityCardId, GetSetUpCardId, @@ -44,7 +44,7 @@ describe('useSetUpCardSections', () => { id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow, }, }, - } as Record>; + } as ActiveCards; const sections = result.current.setUpSections({ activeCards, diff --git a/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.tsx b/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.tsx index 082aadeae88ee..bd762042196ee 100644 --- a/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.tsx +++ b/x-pack/plugins/serverless_security/public/components/get_started/use_setup_cards.tsx @@ -9,7 +9,7 @@ import { EuiSpacer, EuiThemeComputed } from '@elastic/eui'; import React, { useCallback } from 'react'; import { css } from '@emotion/react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; -import { ActiveCard, CardId, SectionId, StepId } from './types'; +import { ActiveCards, CardId, SectionId, StepId } from './types'; import { CardItem } from './card_item'; import { getSections } from './sections'; @@ -30,7 +30,7 @@ export const useSetUpCardSections = ({ }: { onStepClicked: (params: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => void; finishedSteps: Record>; - activeCards: Record> | null; + activeCards: ActiveCards | null; sectionId: SectionId; }) => { const section = activeCards?.[sectionId]; @@ -63,7 +63,7 @@ export const useSetUpCardSections = ({ }: { onStepClicked: (params: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => void; finishedSteps: Record>; - activeCards: Record> | null; + activeCards: ActiveCards | null; }) => getSections().reduce((acc, currentSection) => { const cardNodes = setUpCards({ diff --git a/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.test.tsx b/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.test.tsx new file mode 100644 index 0000000000000..6201009395935 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.test.tsx @@ -0,0 +1,239 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useTogglePanel } from './use_toggle_panel'; +import { getStartedStorage } from '../../lib/get_started/storage'; +import { ProductLine, SecurityProductTypes } from '../../../common/config'; +import { + GetMoreFromElasticSecurityCardId, + GetSetUpCardId, + IntroductionSteps, + SectionId, +} from './types'; + +jest.mock('../../lib/get_started/storage'); + +describe('useTogglePanel', () => { + const productTypes = [ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'complete' }, + ] as SecurityProductTypes; + + beforeEach(() => { + jest.clearAllMocks(); + + (getStartedStorage.getAllFinishedStepsFromStorage as jest.Mock).mockReturnValue({ + [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), + }); + (getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([ + ProductLine.security, + ProductLine.cloud, + ProductLine.endpoint, + ]); + }); + + test('should initialize state with correct initial values - when no active products from local storage', () => { + (getStartedStorage.getAllFinishedStepsFromStorage as jest.Mock).mockReturnValue({}); + (getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([]); + + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { state } = result.current; + + expect(state.activeProducts).toEqual(new Set([ProductLine.security, ProductLine.endpoint])); + expect(state.finishedSteps).toEqual({}); + + expect(state.activeCards).toEqual( + expect.objectContaining({ + [SectionId.getSetUp]: { + [GetSetUpCardId.introduction]: { + id: GetSetUpCardId.introduction, + timeInMins: 3, + stepsLeft: 1, + }, + [GetSetUpCardId.activateAndCreateRules]: { + id: GetSetUpCardId.activateAndCreateRules, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.bringInYourData]: { + id: GetSetUpCardId.bringInYourData, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.protectYourEnvironmentInRealtime]: { + id: GetSetUpCardId.protectYourEnvironmentInRealtime, + timeInMins: 0, + stepsLeft: 0, + }, + }, + [SectionId.getMoreFromElasticSecurity]: { + [GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: { + id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: { + id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.respondToThreats]: { + id: GetMoreFromElasticSecurityCardId.respondToThreats, + timeInMins: 0, + stepsLeft: 0, + }, + }, + }) + ); + }); + + test('should initialize state with correct initial values - when all products active', () => { + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { state } = result.current; + + expect(state.activeProducts).toEqual( + new Set([ProductLine.security, ProductLine.cloud, ProductLine.endpoint]) + ); + expect(state.finishedSteps).toEqual({ + [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), + }); + + expect(state.activeCards).toEqual( + expect.objectContaining({ + [SectionId.getSetUp]: { + [GetSetUpCardId.introduction]: { + id: GetSetUpCardId.introduction, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.activateAndCreateRules]: { + id: GetSetUpCardId.activateAndCreateRules, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.bringInYourData]: { + id: GetSetUpCardId.bringInYourData, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.protectYourEnvironmentInRealtime]: { + id: GetSetUpCardId.protectYourEnvironmentInRealtime, + timeInMins: 0, + stepsLeft: 0, + }, + }, + [SectionId.getMoreFromElasticSecurity]: { + [GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: { + id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: { + id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.respondToThreats]: { + id: GetMoreFromElasticSecurityCardId.respondToThreats, + timeInMins: 0, + stepsLeft: 0, + }, + }, + }) + ); + }); + + test('should initialize state with correct initial values - when only security product active', () => { + (getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([ + ProductLine.security, + ]); + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { state } = result.current; + + expect(state.activeProducts).toEqual(new Set([ProductLine.security])); + expect(state.finishedSteps).toEqual({ + [GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]), + }); + + expect(state.activeCards).toEqual( + expect.objectContaining({ + [SectionId.getSetUp]: { + [GetSetUpCardId.introduction]: { + id: GetSetUpCardId.introduction, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.activateAndCreateRules]: { + id: GetSetUpCardId.activateAndCreateRules, + timeInMins: 0, + stepsLeft: 0, + }, + [GetSetUpCardId.bringInYourData]: { + id: GetSetUpCardId.bringInYourData, + timeInMins: 0, + stepsLeft: 0, + }, + }, + [SectionId.getMoreFromElasticSecurity]: { + [GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: { + id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: { + id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace, + timeInMins: 0, + stepsLeft: 0, + }, + [GetMoreFromElasticSecurityCardId.respondToThreats]: { + id: GetMoreFromElasticSecurityCardId.respondToThreats, + timeInMins: 0, + stepsLeft: 0, + }, + }, + }) + ); + }); + + test('should call addFinishedStepToStorage', () => { + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { onStepClicked } = result.current; + + act(() => { + onStepClicked({ + stepId: IntroductionSteps.watchOverviewVideo, + cardId: GetSetUpCardId.introduction, + sectionId: SectionId.getSetUp, + }); + }); + + expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledTimes(1); + expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledWith( + GetSetUpCardId.introduction, + IntroductionSteps.watchOverviewVideo + ); + }); + + test('should call toggleActiveProductsInStorage', () => { + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { onProductSwitchChanged } = result.current; + + act(() => { + onProductSwitchChanged({ id: ProductLine.security, label: 'Analytics' }); + }); + + expect(getStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledTimes(1); + expect(getStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith( + ProductLine.security + ); + }); +}); diff --git a/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.tsx b/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.tsx new file mode 100644 index 0000000000000..f449478572c45 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/components/get_started/use_toggle_panel.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback, useMemo, useReducer } from 'react'; +import { ProductLine, SecurityProductTypes } from '../../../common/config'; +import { getStartedStorage } from '../../lib/get_started/storage'; +import { + getActiveCardsInitialStates, + getActiveSectionsInitialStates, + getFinishedStepsInitialStates, + reducer, +} from './reducer'; +import { CardId, GetStartedPageActions, SectionId, StepId, Switch } from './types'; + +export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProductTypes }) => { + const { + getAllFinishedStepsFromStorage, + getActiveProductsFromStorage, + toggleActiveProductsInStorage, + addFinishedStepToStorage, + } = getStartedStorage; + + const finishedStepsInitialStates = useMemo( + () => getFinishedStepsInitialStates({ finishedSteps: getAllFinishedStepsFromStorage() }), + [getAllFinishedStepsFromStorage] + ); + + const activeSectionsInitialStates = useMemo(() => { + const activeProductsFromStorage = getActiveSectionsInitialStates({ + activeProducts: getActiveProductsFromStorage(), + }); + return activeProductsFromStorage.size > 0 + ? activeProductsFromStorage + : new Set(productTypes.map(({ product_line: productLine }) => ProductLine[productLine])) ?? + new Set([ProductLine.security, ProductLine.endpoint, ProductLine.cloud]); + }, [getActiveProductsFromStorage, productTypes]); + + const activeCardsInitialStates = useMemo( + () => + getActiveCardsInitialStates({ + activeProducts: activeSectionsInitialStates, + finishedSteps: finishedStepsInitialStates, + }), + [activeSectionsInitialStates, finishedStepsInitialStates] + ); + + const [state, dispatch] = useReducer(reducer, { + activeProducts: activeSectionsInitialStates, + finishedSteps: finishedStepsInitialStates, + activeCards: activeCardsInitialStates, + }); + + const onStepClicked = useCallback( + ({ stepId, cardId, sectionId }: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => { + dispatch({ + type: GetStartedPageActions.AddFinishedStep, + payload: { stepId, cardId, sectionId }, + }); + addFinishedStepToStorage(cardId, stepId); + }, + [addFinishedStepToStorage] + ); + + const onProductSwitchChanged = useCallback( + (section: Switch) => { + dispatch({ type: GetStartedPageActions.ToggleProduct, payload: { section: section.id } }); + toggleActiveProductsInStorage(section.id); + }, + [toggleActiveProductsInStorage] + ); + + return { state, onStepClicked, onProductSwitchChanged }; +}; diff --git a/x-pack/plugins/serverless_security/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_security/public/components/side_navigation/index.tsx index db621f7fb6a02..399d6ecab13de 100644 --- a/x-pack/plugins/serverless_security/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_security/public/components/side_navigation/index.tsx @@ -5,22 +5,14 @@ * 2.0. */ import React from 'react'; -import { CoreStart } from '@kbn/core/public'; -import type { - SideNavComponent, - SideNavCompProps, -} from '@kbn/core-chrome-browser/src/project_navigation'; -import { ServerlessSecurityPluginStartDependencies } from '../../types'; +import type { SideNavComponent } from '@kbn/core-chrome-browser/src/project_navigation'; import { SecuritySideNavigation } from './lazy'; -import { KibanaServicesProvider } from '../../services'; +import { KibanaServicesProvider, type Services } from '../../common/services'; -export const getSecuritySideNavComponent = ( - core: CoreStart, - pluginsStart: ServerlessSecurityPluginStartDependencies -): SideNavComponent => { - return (_props: SideNavCompProps) => ( - +export const getSecuritySideNavComponent = (services: Services): SideNavComponent => { + return () => ( + ); diff --git a/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.test.tsx b/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.test.tsx index 5309dbf3e1295..eef0ccb8da671 100644 --- a/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { SecuritySideNavigation } from './side_navigation'; import { useSideNavItems, useSideNavSelectedId } from '../../hooks/use_side_nav_items'; import { SecurityPageName } from '@kbn/security-solution-plugin/common'; -import { KibanaServicesProvider } from '../../services.mock'; +import { KibanaServicesProvider } from '../../common/services.mock'; jest.mock('../../hooks/use_side_nav_items'); const mockUseSideNavItems = useSideNavItems as jest.Mock; diff --git a/x-pack/plugins/serverless_security/public/components/upselling/register_upsellings.test.tsx b/x-pack/plugins/serverless_security/public/components/upselling/register_upsellings.test.tsx index b2a1390ee098f..304ee02de256c 100644 --- a/x-pack/plugins/serverless_security/public/components/upselling/register_upsellings.test.tsx +++ b/x-pack/plugins/serverless_security/public/components/upselling/register_upsellings.test.tsx @@ -8,7 +8,7 @@ import { UpsellingService } from '@kbn/security-solution-plugin/public'; import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common'; import { registerUpsellings, upsellingPages, upsellingSections } from './register_upsellings'; -import type { SecurityProductTypes } from '../../../common/config'; +import { ProductLine, SecurityProductTypes } from '../../../common/config'; const mockGetProductAppFeatures = jest.fn(); jest.mock('../../../common/pli/pli_features', () => ({ @@ -16,9 +16,9 @@ jest.mock('../../../common/pli/pli_features', () => ({ })); const allProductTypes: SecurityProductTypes = [ - { product_line: 'security', product_tier: 'complete' }, - { product_line: 'endpoint', product_tier: 'complete' }, - { product_line: 'cloud', product_tier: 'complete' }, + { product_line: ProductLine.security, product_tier: 'complete' }, + { product_line: ProductLine.endpoint, product_tier: 'complete' }, + { product_line: ProductLine.cloud, product_tier: 'complete' }, ]; describe('registerUpsellings', () => { diff --git a/x-pack/plugins/serverless_security/public/hooks/use_link_props.test.tsx b/x-pack/plugins/serverless_security/public/hooks/use_link_props.test.tsx index b04f19be75b8f..2a20ca97ef855 100644 --- a/x-pack/plugins/serverless_security/public/hooks/use_link_props.test.tsx +++ b/x-pack/plugins/serverless_security/public/hooks/use_link_props.test.tsx @@ -8,13 +8,13 @@ import { MouseEvent } from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { APP_UI_ID, SecurityPageName } from '@kbn/security-solution-plugin/common'; -import { KibanaServicesProvider, servicesMocks } from '../services.mock'; +import { KibanaServicesProvider, servicesMocks } from '../common/services.mock'; import { useGetLinkProps, useLinkProps } from './use_link_props'; -const { getUrlForApp: mockGetUrlForApp, navigateToUrl: mockNavigateToUrl } = - servicesMocks.application; +const { getUrlForApp, navigateToUrl: mockNavigateToUrl } = servicesMocks.application; const href = '/app/security/test'; +const mockGetUrlForApp = getUrlForApp as jest.MockedFunction; mockGetUrlForApp.mockReturnValue(href); describe('useLinkProps', () => { diff --git a/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts b/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts index 3a1989dbdc79a..6644afb7fdf01 100644 --- a/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts +++ b/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts @@ -7,7 +7,7 @@ import { APP_UI_ID, type SecurityPageName } from '@kbn/security-solution-plugin/common'; import { useMemo, useCallback, type MouseEventHandler, type MouseEvent } from 'react'; -import { useKibana, type Services } from '../services'; +import { useKibana, type Services } from '../common/services'; interface LinkProps { onClick: MouseEventHandler; diff --git a/x-pack/plugins/serverless_security/public/hooks/use_nav_links.ts b/x-pack/plugins/serverless_security/public/hooks/use_nav_links.ts index 7d2b16f6cc6e8..eaa643603f86d 100644 --- a/x-pack/plugins/serverless_security/public/hooks/use_nav_links.ts +++ b/x-pack/plugins/serverless_security/public/hooks/use_nav_links.ts @@ -7,11 +7,10 @@ import { useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { useKibana } from '../services'; +import { useKibana } from '../common/services'; export const useNavLinks = () => { - const { securitySolution } = useKibana().services; - const { getNavLinks$ } = securitySolution; - const navLinks$ = useMemo(() => getNavLinks$(), [getNavLinks$]); - return useObservable(navLinks$, []); + const { getProjectNavLinks$ } = useKibana().services; + const projectNavLinks$ = useMemo(() => getProjectNavLinks$(), [getProjectNavLinks$]); + return useObservable(projectNavLinks$, []); }; diff --git a/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.test.tsx b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.test.tsx index 38a1f4e578427..22c0e7118ec76 100644 --- a/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.test.tsx +++ b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.test.tsx @@ -7,18 +7,15 @@ import { renderHook } from '@testing-library/react-hooks'; import { useSideNavItems, useSideNavSelectedId } from './use_side_nav_items'; -import { BehaviorSubject } from 'rxjs'; -import type { NavigationLink } from '@kbn/security-solution-plugin/public/common/links/types'; import { SecurityPageName } from '@kbn/security-solution-plugin/common'; -import { KibanaServicesProvider, servicesMocks } from '../services.mock'; +import { + KibanaServicesProvider, + servicesMocks, + mockProjectNavLinks, +} from '../common/services.mock'; jest.mock('./use_link_props'); -const mockNavLinks = jest.fn((): NavigationLink[] => []); -servicesMocks.securitySolution.getNavLinks$.mockImplementation( - () => new BehaviorSubject(mockNavLinks()) -); - const mockUseLocation = jest.fn(() => ({ pathname: '/' })); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -36,11 +33,11 @@ describe('useSideNavItems', () => { const items = result.current; expect(items).toEqual([]); - expect(servicesMocks.securitySolution.getNavLinks$).toHaveBeenCalledTimes(1); + expect(servicesMocks.getProjectNavLinks$).toHaveBeenCalledTimes(1); }); it('should return main items', async () => { - mockNavLinks.mockReturnValueOnce([ + mockProjectNavLinks.mockReturnValueOnce([ { id: SecurityPageName.alerts, title: 'Alerts' }, { id: SecurityPageName.case, title: 'Cases' }, ]); @@ -66,7 +63,7 @@ describe('useSideNavItems', () => { }); it('should return secondary items', async () => { - mockNavLinks.mockReturnValueOnce([ + mockProjectNavLinks.mockReturnValueOnce([ { id: SecurityPageName.dashboards, title: 'Dashboards', @@ -96,7 +93,7 @@ describe('useSideNavItems', () => { }); it('should return get started link', async () => { - mockNavLinks.mockReturnValueOnce([ + mockProjectNavLinks.mockReturnValueOnce([ { id: SecurityPageName.landing, title: 'Get Started', diff --git a/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts index d352a779e9444..7ec68683eb223 100644 --- a/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts +++ b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts @@ -8,8 +8,11 @@ import { useMemo } from 'react'; import { matchPath, useLocation } from 'react-router-dom'; import { SecurityPageName } from '@kbn/security-solution-plugin/common'; -import { SolutionSideNavItem, SolutionSideNavItemPosition } from '@kbn/security-solution-side-nav'; -import { useKibana } from '../services'; +import { + SolutionSideNavItemPosition, + type SolutionSideNavItem, +} from '@kbn/security-solution-side-nav'; +import { useKibana } from '../common/services'; import { type GetLinkProps, useGetLinkProps } from './use_link_props'; import { useNavLinks } from './use_nav_links'; diff --git a/x-pack/plugins/serverless_security/public/lib/get_started/storage.test.ts b/x-pack/plugins/serverless_security/public/lib/get_started/storage.test.ts index 158c271470768..e55c4ef28948b 100644 --- a/x-pack/plugins/serverless_security/public/lib/get_started/storage.test.ts +++ b/x-pack/plugins/serverless_security/public/lib/get_started/storage.test.ts @@ -6,14 +6,10 @@ */ import { getStartedStorage } from './storage'; -import { - GetSetUpCardId, - IntroductionSteps, - ProductId, - StepId, -} from '../../components/get_started/types'; +import { GetSetUpCardId, IntroductionSteps, StepId } from '../../components/get_started/types'; import { storage } from '../storage'; import { MockStorage } from '../__mocks__/storage'; +import { ProductLine } from '../../../common/config'; jest.mock('../storage'); @@ -33,13 +29,13 @@ describe('useStorage', () => { }); it('should toggle active products in storage', () => { - expect(getStartedStorage.toggleActiveProductsInStorage(ProductId.analytics)).toEqual([ - ProductId.analytics, + expect(getStartedStorage.toggleActiveProductsInStorage(ProductLine.security)).toEqual([ + ProductLine.security, ]); - expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', [ProductId.analytics]); + expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', [ProductLine.security]); - mockStorage.set('ACTIVE_PRODUCTS', [ProductId.analytics]); - expect(getStartedStorage.toggleActiveProductsInStorage(ProductId.analytics)).toEqual([]); + mockStorage.set('ACTIVE_PRODUCTS', [ProductLine.security]); + expect(getStartedStorage.toggleActiveProductsInStorage(ProductLine.security)).toEqual([]); expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', []); }); diff --git a/x-pack/plugins/serverless_security/public/lib/get_started/storage.ts b/x-pack/plugins/serverless_security/public/lib/get_started/storage.ts index 691cbb5f102f0..1d9c52813c832 100644 --- a/x-pack/plugins/serverless_security/public/lib/get_started/storage.ts +++ b/x-pack/plugins/serverless_security/public/lib/get_started/storage.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { CardId, ProductId, StepId } from '../../components/get_started/types'; +import { ProductLine } from '../../../common/config'; +import { CardId, StepId } from '../../components/get_started/types'; import { storage } from '../storage'; export const ACTIVE_PRODUCTS_STORAGE_KEY = 'ACTIVE_PRODUCTS'; @@ -13,12 +14,12 @@ export const FINISHED_STEPS_STORAGE_KEY = 'FINISHED_STEPS'; export const getStartedStorage = { getActiveProductsFromStorage: () => { - const activeProducts: ProductId[] = storage.get(ACTIVE_PRODUCTS_STORAGE_KEY); + const activeProducts: ProductLine[] = storage.get(ACTIVE_PRODUCTS_STORAGE_KEY); return activeProducts ?? new Array(); }, - toggleActiveProductsInStorage: (productId: ProductId) => { - const activeProducts: ProductId[] = - storage.get(ACTIVE_PRODUCTS_STORAGE_KEY) ?? new Array(); + toggleActiveProductsInStorage: (productId: ProductLine) => { + const activeProducts: ProductLine[] = + storage.get(ACTIVE_PRODUCTS_STORAGE_KEY) ?? new Array(); const index = activeProducts.indexOf(productId); if (index < 0) { activeProducts.push(productId); diff --git a/x-pack/plugins/serverless_security/public/plugin.ts b/x-pack/plugins/serverless_security/public/plugin.ts index f0729330de4af..acc7854a363b0 100644 --- a/x-pack/plugins/serverless_security/public/plugin.ts +++ b/x-pack/plugins/serverless_security/public/plugin.ts @@ -17,6 +17,9 @@ import { ServerlessSecurityPublicConfig, } from './types'; import { registerUpsellings } from './components/upselling'; +import { createServices } from './common/services'; +import { subscribeNavigationTree } from './common/navigation/navigation_tree'; +import { subscribeBreadcrumbs } from './common/navigation/breadcrumbs'; export class ServerlessSecurityPlugin implements @@ -45,12 +48,21 @@ export class ServerlessSecurityPlugin core: CoreStart, startDeps: ServerlessSecurityPluginStartDependencies ): ServerlessSecurityPluginStart { - const { securitySolution, serverless } = startDeps; + const { securitySolution, serverless, management } = startDeps; + const { productTypes } = this.config; + + const services = createServices(core, startDeps); securitySolution.setIsSidebarEnabled(false); - securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps)); + securitySolution.setGetStartedPage(getSecurityGetStartedComponent(services, productTypes)); + serverless.setProjectHome('/app/security'); - serverless.setSideNavComponent(getSecuritySideNavComponent(core, startDeps)); + serverless.setSideNavComponent(getSecuritySideNavComponent(services)); + + subscribeNavigationTree(services); + subscribeBreadcrumbs(services); + + management.setLandingPageRedirect('/app/security/manage'); return {}; } diff --git a/x-pack/plugins/serverless_security/public/types.ts b/x-pack/plugins/serverless_security/public/types.ts index 3954183cc9749..73052810127ad 100644 --- a/x-pack/plugins/serverless_security/public/types.ts +++ b/x-pack/plugins/serverless_security/public/types.ts @@ -11,6 +11,7 @@ import type { PluginStart as SecuritySolutionPluginStart, } from '@kbn/security-solution-plugin/public'; import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { SecurityProductTypes } from '../common/config'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -23,12 +24,14 @@ export interface ServerlessSecurityPluginSetupDependencies { security: SecurityPluginSetup; securitySolution: SecuritySolutionPluginSetup; serverless: ServerlessPluginSetup; + management: ManagementSetup; } export interface ServerlessSecurityPluginStartDependencies { security: SecurityPluginStart; securitySolution: SecuritySolutionPluginStart; serverless: ServerlessPluginStart; + management: ManagementStart; } export interface ServerlessSecurityPublicConfig { diff --git a/x-pack/plugins/serverless_security/tsconfig.json b/x-pack/plugins/serverless_security/tsconfig.json index 69d976d324105..2d2d3b8d34e6a 100644 --- a/x-pack/plugins/serverless_security/tsconfig.json +++ b/x-pack/plugins/serverless_security/tsconfig.json @@ -17,6 +17,7 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", + "@kbn/management-plugin", "@kbn/security-plugin", "@kbn/security-solution-plugin", "@kbn/serverless", diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index ee4bbb40891e8..42c16c40baf5c 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -51,7 +51,7 @@ export const SessionView = ({ jumpToCursor, investigatedAlertId, loadAlertDetails, - canAccessEndpointManagement, + canReadPolicyManagement, }: SessionViewDeps) => { // don't engage jumpTo if jumping to session leader. if (jumpToEntityId === sessionEntityId) { @@ -435,7 +435,7 @@ export const SessionView = ({ isFullscreen={isFullScreen} onJumpToEvent={onJumpToEvent} autoSeekToEntityId={currentJumpToOutputEntityId} - canAccessEndpointManagement={canAccessEndpointManagement} + canReadPolicyManagement={canReadPolicyManagement} />
); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx index a3a17380c8fc9..42be993d39d1d 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx @@ -107,9 +107,7 @@ describe('TTYPlayer component', () => { }); it('renders a message warning when max_bytes exceeded with link to policies page', async () => { - renderResult = mockedContext.render( - - ); + renderResult = mockedContext.render(); await waitForApiCall(); await new Promise((r) => setTimeout(r, 10)); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.tsx index 36d685371e023..aa85f4bd794c0 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.tsx @@ -41,7 +41,7 @@ export interface TTYPlayerDeps { isFullscreen: boolean; onJumpToEvent(event: ProcessEvent): void; autoSeekToEntityId?: string; - canAccessEndpointManagement?: boolean; + canReadPolicyManagement?: boolean; } export const TTYPlayer = ({ @@ -53,7 +53,7 @@ export const TTYPlayer = ({ isFullscreen, onJumpToEvent, autoSeekToEntityId, - canAccessEndpointManagement, + canReadPolicyManagement, }: TTYPlayerDeps) => { const ref = useRef(null); const { ref: scrollRef, height: containerHeight = 1 } = useResizeObserver({}); @@ -71,10 +71,8 @@ export const TTYPlayer = ({ const { getUrlForApp } = useKibana().services.application; const policiesUrl = useMemo( () => - canAccessEndpointManagement - ? getUrlForApp(SECURITY_APP_ID, { path: POLICIES_PAGE_PATH }) - : '', - [canAccessEndpointManagement, getUrlForApp] + canReadPolicyManagement ? getUrlForApp(SECURITY_APP_ID, { path: POLICIES_PAGE_PATH }) : '', + [canReadPolicyManagement, getUrlForApp] ); const { search, currentLine, seekToLine } = useXtermPlayer({ diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 3783abdfd2e8b..846d3baaa86ef 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -34,7 +34,7 @@ export interface SessionViewDeps { // Callback used when alert flyout panel is closed handleOnAlertDetailsClosed: () => void ) => void; - canAccessEndpointManagement?: boolean; + canReadPolicyManagement?: boolean; } export interface EuiTabProps { diff --git a/x-pack/plugins/spaces/public/config.ts b/x-pack/plugins/spaces/public/config.ts index 1f0565016bfe5..567c01c66cfe1 100644 --- a/x-pack/plugins/spaces/public/config.ts +++ b/x-pack/plugins/spaces/public/config.ts @@ -7,4 +7,5 @@ export interface ConfigType { maxSpaces: number; + allowFeatureVisibility: boolean; } diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index 071f02e6d3114..ccb5412aa39c6 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -20,6 +20,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { SpacesManager } from '../../spaces_manager'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; +import { EnabledFeatures } from './enabled_features'; import { ManageSpacePage } from './manage_space_page'; // To be resolved by EUI team. @@ -74,6 +75,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); @@ -103,6 +105,64 @@ describe('ManageSpacePage', () => { }); }); + it('shows feature visibility controls when allowed', async () => { + const spacesManager = spacesManagerMock.create(); + spacesManager.createSpace = jest.fn(spacesManager.createSpace); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + + const wrapper = mountWithIntl( + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); + + expect(wrapper.find(EnabledFeatures)).toHaveLength(1); + }); + + it('hides feature visibility controls when not allowed', async () => { + const spacesManager = spacesManagerMock.create(); + spacesManager.createSpace = jest.fn(spacesManager.createSpace); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + + const wrapper = mountWithIntl( + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); + + expect(wrapper.find(EnabledFeatures)).toHaveLength(0); + }); + it('allows a space to be updated', async () => { const spaceToUpdate = { id: 'existing-space', @@ -135,6 +195,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); @@ -202,6 +263,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); @@ -250,6 +312,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); @@ -286,6 +349,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); @@ -346,6 +410,7 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + allowFeatureVisibility /> ); diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index f2892916d296f..143f4fce34d26 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -54,6 +54,7 @@ interface Props { onLoadSpace?: (space: Space) => void; capabilities: Capabilities; history: ScopedHistory; + allowFeatureVisibility: boolean; } interface State { @@ -161,13 +162,16 @@ export class ManageSpacePage extends Component { validator={this.validator} /> - - - + {this.props.allowFeatureVisibility && ( + <> + + + + )} diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts index 251707dd95498..105466e42827e 100644 --- a/x-pack/plugins/spaces/public/management/management_service.test.ts +++ b/x-pack/plugins/spaces/public/management/management_service.test.ts @@ -18,6 +18,7 @@ import { ManagementService } from './management_service'; describe('ManagementService', () => { const config: ConfigType = { maxSpaces: 1000, + allowFeatureVisibility: true, }; describe('#setup', () => { diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 2b05b8a64c92d..fecc81077d690 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -28,6 +28,7 @@ import { spacesManagementApp } from './spaces_management_app'; const config: ConfigType = { maxSpaces: 1000, + allowFeatureVisibility: true, }; async function mountApp(basePath: string, pathname: string, spaceId?: string) { @@ -119,7 +120,7 @@ describe('spacesManagementApp', () => {
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}}} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true}
`); @@ -151,7 +152,7 @@ describe('spacesManagementApp', () => {
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}}} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true}

`); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index d898dbc69035e..ba63168875d75 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -83,6 +83,7 @@ export const spacesManagementApp = Object.freeze({ notifications={notifications} spacesManager={spacesManager} history={history} + allowFeatureVisibility={config.allowFeatureVisibility} /> ); }; @@ -108,6 +109,7 @@ export const spacesManagementApp = Object.freeze({ spaceId={spaceId} onLoadSpace={onLoadSpace} history={history} + allowFeatureVisibility={config.allowFeatureVisibility} /> ); }; diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index 995d3e44cc3f3..bc3edf8cf4e5b 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -20,6 +20,7 @@ describe('config schema', () => { it('generates proper defaults', () => { expect(ConfigSchema.validate({})).toMatchInlineSnapshot(` Object { + "allowFeatureVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -27,6 +28,7 @@ describe('config schema', () => { expect(ConfigSchema.validate({}, { dev: false })).toMatchInlineSnapshot(` Object { + "allowFeatureVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -34,6 +36,7 @@ describe('config schema', () => { expect(ConfigSchema.validate({}, { dev: true })).toMatchInlineSnapshot(` Object { + "allowFeatureVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -53,4 +56,24 @@ describe('config schema', () => { it('should not throw error if spaces is disabled in development mode', () => { expect(() => ConfigSchema.validate({ enabled: false }, { dev: true })).not.toThrow(); }); + + it('should throw error if allowFeatureVisibility is disabled in classic offering', () => { + expect(() => ConfigSchema.validate({ allowFeatureVisibility: false }, {})).toThrow(); + }); + + it('should not throw error if allowFeatureVisibility is disabled in serverless offering', () => { + expect(() => + ConfigSchema.validate({ allowFeatureVisibility: false }, { serverless: true }) + ).not.toThrow(); + }); + + it('should not throw error if allowFeatureVisibility is enabled in classic offering', () => { + expect(() => ConfigSchema.validate({ allowFeatureVisibility: true }, {})).not.toThrow(); + }); + + it('should throw error if allowFeatureVisibility is enabled in serverless offering', () => { + expect(() => + ConfigSchema.validate({ allowFeatureVisibility: true }, { serverless: true }) + ).toThrow(); + }); }); diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts index 5e9b6764e6982..e4a2852a01dc8 100644 --- a/x-pack/plugins/spaces/server/config.ts +++ b/x-pack/plugins/spaces/server/config.ts @@ -26,6 +26,22 @@ export const ConfigSchema = schema.object({ }) ), maxSpaces: schema.number({ defaultValue: 1000 }), + allowFeatureVisibility: schema.conditional( + schema.contextRef('serverless'), + true, + schema.literal(false), + schema.boolean({ + validate: (rawValue) => { + // This setting should not be configurable on-prem to avoid bugs when e.g. existing spaces + // have feature visibility customized but admins would be unable to change them back if the + // UI/APIs are disabled. + if (rawValue === false) { + return 'Feature visibility can only be disabled on serverless'; + } + }, + defaultValue: true, + }) + ), }); export function createConfig$(context: PluginInitializerContext) { diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts index 6c3794f202e3c..b7b97692f803e 100644 --- a/x-pack/plugins/spaces/server/index.ts +++ b/x-pack/plugins/spaces/server/index.ts @@ -33,6 +33,7 @@ export const config: PluginConfigDescriptor = { schema: ConfigSchema, exposeToBrowser: { maxSpaces: true, + allowFeatureVisibility: true, }, }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index 8107b6f341745..57a724a16ece8 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -130,6 +130,7 @@ describe('Spaces Public API', () => { id: 'a-space', name: 'my updated space', description: 'with a description', + disabledFeatures: [], }; const { routeHandler } = await setup(); @@ -152,6 +153,7 @@ describe('Spaces Public API', () => { id: 'my-space-id', name: 'my new space', description: 'with a description', + disabledFeatures: [], }; const { routeValidation, routeHandler, savedObjectsRepositoryMock } = await setup(); diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 5c82f0dd48ed9..ac85d14989ebb 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -175,6 +175,7 @@ describe('PUT /api/spaces/space', () => { id: 'a-new-space', name: 'my new space', description: 'with a description', + disabledFeatures: [], }; const { routeHandler } = await setup(); diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index ff63f71e613df..709faff41c477 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -17,8 +17,10 @@ const createMockDebugLogger = () => { return jest.fn(); }; -const createMockConfig = (mockConfig: ConfigType = { enabled: true, maxSpaces: 1000 }) => { - return ConfigSchema.validate(mockConfig); +const createMockConfig = ( + mockConfig: ConfigType = { enabled: true, maxSpaces: 1000, allowFeatureVisibility: true } +) => { + return ConfigSchema.validate(mockConfig, { serverless: !mockConfig.allowFeatureVisibility }); }; describe('#getAll', () => { @@ -97,7 +99,7 @@ describe('#getAll', () => { mockCallWithRequestRepository.find.mockResolvedValue({ saved_objects: savedObjects, } as any); - const mockConfig = createMockConfig({ enabled: true, maxSpaces: 1234 }); + const mockConfig = createMockConfig(); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const actualSpaces = await client.getAll(); @@ -207,7 +209,7 @@ describe('#create', () => { total: maxSpaces - 1, } as any); - const mockConfig = createMockConfig({ enabled: true, maxSpaces }); + const mockConfig = createMockConfig({ enabled: true, maxSpaces, allowFeatureVisibility: true }); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); @@ -233,7 +235,7 @@ describe('#create', () => { total: maxSpaces, } as any); - const mockConfig = createMockConfig({ enabled: true, maxSpaces }); + const mockConfig = createMockConfig({ enabled: true, maxSpaces, allowFeatureVisibility: true }); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); @@ -248,6 +250,79 @@ describe('#create', () => { }); expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled(); }); + + describe('when config.allowFeatureVisibility is disabled', () => { + test(`creates space without disabledFeatures`, async () => { + const maxSpaces = 5; + const mockDebugLogger = createMockDebugLogger(); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.create.mockResolvedValue(savedObject); + mockCallWithRequestRepository.find.mockResolvedValue({ + total: maxSpaces - 1, + } as any); + + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces, + allowFeatureVisibility: false, + }); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [] + ); + + const actualSpace = await client.create(spaceToCreate); + + expect(actualSpace).toEqual(expectedReturnedSpace); + expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ + type: 'space', + page: 1, + perPage: 0, + }); + expect(mockCallWithRequestRepository.create).toHaveBeenCalledWith('space', attributes, { + id, + }); + }); + + test(`throws bad request when creating space with disabledFeatures`, async () => { + const maxSpaces = 5; + const mockDebugLogger = createMockDebugLogger(); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.create.mockResolvedValue(savedObject); + mockCallWithRequestRepository.find.mockResolvedValue({ + total: maxSpaces - 1, + } as any); + + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces, + allowFeatureVisibility: false, + }); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [] + ); + + expect( + client.create({ ...spaceToCreate, disabledFeatures: ['some-feature'] }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to create Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled"` + ); + + expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ + type: 'space', + page: 1, + perPage: 0, + }); + expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled(); + }); + }); }); describe('#update', () => { @@ -298,6 +373,60 @@ describe('#update', () => { expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, attributes); expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id); }); + + describe('when config.allowFeatureVisibility is disabled', () => { + test(`updates space without disabledFeatures`, async () => { + const mockDebugLogger = createMockDebugLogger(); + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces: 1000, + allowFeatureVisibility: false, + }); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.get.mockResolvedValue(savedObject); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [] + ); + const id = savedObject.id; + const actualSpace = await client.update(id, spaceToUpdate); + + expect(actualSpace).toEqual(expectedReturnedSpace); + expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, attributes); + expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id); + }); + + test(`throws bad request when updating space with disabledFeatures`, async () => { + const mockDebugLogger = createMockDebugLogger(); + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces: 1000, + allowFeatureVisibility: false, + }); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.get.mockResolvedValue(savedObject); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [] + ); + const id = savedObject.id; + + expect( + client.update(id, { ...spaceToUpdate, disabledFeatures: ['some-feature'] }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled"` + ); + + expect(mockCallWithRequestRepository.update).not.toHaveBeenCalled(); + expect(mockCallWithRequestRepository.get).not.toHaveBeenCalled(); + }); + }); }); describe('#delete', () => { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index f1d7c271353a2..a09ddf8ad3f38 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -124,6 +124,12 @@ export class SpacesClient implements ISpacesClient { ); } + if (space.disabledFeatures.length > 0 && !this.config.allowFeatureVisibility) { + throw Boom.badRequest( + 'Unable to create Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled' + ); + } + this.debugLogger(`SpacesClient.create(), using RBAC. Attempting to create space`); const id = space.id; @@ -136,6 +142,12 @@ export class SpacesClient implements ISpacesClient { } public async update(id: string, space: v1.Space) { + if (space.disabledFeatures.length > 0 && !this.config.allowFeatureVisibility) { + throw Boom.badRequest( + 'Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled' + ); + } + const attributes = this.generateSpaceAttributes(space); await this.repository.update('space', id, attributes); const updatedSavedObject = await this.repository.get('space', id); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts index 078dfa7b45d04..9c2a5b1eb4ff1 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts @@ -7,10 +7,10 @@ import * as t from 'io-ts'; -export const SyntheticsParamSOCodec = t.intersection([ +export const SyntheticsParamsReadonlyCodec = t.intersection([ t.interface({ + id: t.string, key: t.string, - value: t.string, }), t.partial({ description: t.string, @@ -19,7 +19,23 @@ export const SyntheticsParamSOCodec = t.intersection([ }), ]); -export type SyntheticsParamSO = t.TypeOf; +export type SyntheticsParamsReadonly = t.TypeOf; + +export const SyntheticsParamsCodec = t.intersection([ + SyntheticsParamsReadonlyCodec, + t.interface({ value: t.string }), +]); + +export type SyntheticsParams = t.TypeOf; + +export type SyntheticsParamSOAttributes = t.TypeOf; + +export const DeleteParamsResponseCodec = t.interface({ + id: t.string, + deleted: t.boolean, +}); + +export type DeleteParamsResponse = t.TypeOf; export const SyntheticsParamRequestCodec = t.intersection([ t.interface({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx index 4181afdfd6a3e..c79e5aec2177a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx @@ -31,7 +31,7 @@ import { } from '../../../state/global_params'; import { ClientPluginsStart } from '../../../../../plugin'; import { ListParamItem } from './params_list'; -import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; +import { SyntheticsParams } from '../../../../../../common/runtime_types'; import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; import { AddParamForm } from './add_param_form'; import { syncGlobalParamsAction } from '../../../state/settings'; @@ -49,7 +49,7 @@ export const AddParamFlyout = ({ const { id, ...dataToSave } = isEditingItem ?? {}; - const form = useFormWrapped({ + const form = useFormWrapped({ mode: 'onSubmit', reValidateMode: 'onChange', shouldFocusError: true, @@ -77,7 +77,7 @@ export const AddParamFlyout = ({ const { isSaving, savedData } = useSelector(selectGlobalParamState); - const onSubmit = (formData: SyntheticsParamSO) => { + const onSubmit = (formData: SyntheticsParams) => { const { namespaces, ...paramRequest } = formData; const shareAcrossSpaces = namespaces?.includes(ALL_SPACES_ID); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx index 240314dc3b06f..1b219a0f6fec4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Controller, useFormContext, useFormState } from 'react-hook-form'; -import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; +import { SyntheticsParams } from '../../../../../../common/runtime_types'; import { ListParamItem } from './params_list'; export const AddParamForm = ({ @@ -26,8 +26,8 @@ export const AddParamForm = ({ items: ListParamItem[]; isEditingItem: ListParamItem | null; }) => { - const { register, control } = useFormContext(); - const { errors } = useFormState(); + const { register, control } = useFormContext(); + const { errors } = useFormState(); const tagsList = items.reduce((acc, item) => { const tags = item.tags || []; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx index 21d3c158c3326..aa89829380044 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx @@ -35,14 +35,14 @@ export const DeleteParam = ({ const { status } = useFetcher(() => { if (isDeleting) { - return deleteGlobalParams({ ids: items.map(({ id }) => id) }); + return deleteGlobalParams(items.map(({ id }) => id)); } }, [items, isDeleting]); const name = items .map(({ key }) => key) .join(', ') - .substr(0, 50); + .slice(0, 50); useEffect(() => { if (!isDeleting) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx index fc8aef5d56732..58769a7e688fe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx @@ -24,12 +24,12 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { useDebounce } from 'react-use'; import { TableTitle } from '../../common/components/table_title'; import { ParamsText } from './params_text'; -import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; +import { SyntheticsParams } from '../../../../../../common/runtime_types'; import { useParamsList } from '../hooks/use_params_list'; import { AddParamFlyout } from './add_param_flyout'; import { DeleteParam } from './delete_param'; -export interface ListParamItem extends SyntheticsParamSO { +export interface ListParamItem extends SyntheticsParams { id: string; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts index 4f86ca5eb8d80..40006c262923e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts @@ -20,12 +20,7 @@ export const useParamsList = () => { return useMemo(() => { return { - items: - listOfParams?.map((item) => ({ - id: item.id, - ...item.attributes, - namespaces: item.namespaces, - })) ?? [], + items: listOfParams ?? [], isLoading, }; }, [listOfParams, isLoading]); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/certs/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/certs/index.ts index f21df69bcbbce..708e1bb004a74 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/certs/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/certs/index.ts @@ -6,7 +6,7 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { CertResult, SyntheticsParamSO } from '../../../../../common/runtime_types'; +import { CertResult, SyntheticsParams } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError } from '..'; import { getCertsListAction } from './actions'; @@ -15,7 +15,7 @@ export interface CertsListState { data?: CertResult; error: IHttpSerializedFetchError | null; isSaving?: boolean; - savedData?: SyntheticsParamSO; + savedData?: SyntheticsParams; } const initialState: CertsListState = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts index 14b5040282f52..b1388bc2674b9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/actions.ts @@ -5,15 +5,14 @@ * 2.0. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SyntheticsParamRequest, SyntheticsParamSO } from '../../../../../common/runtime_types'; +import { SyntheticsParamRequest, SyntheticsParams } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; -export const getGlobalParamAction = createAsyncAction>>( +export const getGlobalParamAction = createAsyncAction( 'GET GLOBAL PARAMS' ); -export const addNewGlobalParamAction = createAsyncAction( +export const addNewGlobalParamAction = createAsyncAction( 'ADD NEW GLOBAL PARAM' ); @@ -22,5 +21,5 @@ export const editGlobalParamAction = createAsyncAction< id: string; paramRequest: SyntheticsParamRequest; }, - SyntheticsParamSO + SyntheticsParams >('EDIT GLOBAL PARAM'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts index 3fd5e8678d94b..528921f1d5bf4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts @@ -5,23 +5,28 @@ * 2.0. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; -import { SyntheticsParamRequest, SyntheticsParamSO } from '../../../../../common/runtime_types'; +import { + DeleteParamsResponse, + SyntheticsParamRequest, + SyntheticsParams, + SyntheticsParamsCodec, + SyntheticsParamsReadonlyCodec, +} from '../../../../../common/runtime_types'; import { apiService } from '../../../../utils/api_service/api_service'; -export const getGlobalParams = async (): Promise>> => { - const result = (await apiService.get(SYNTHETICS_API_URLS.PARAMS)) as { - data: Array>; - }; - return result.data; +export const getGlobalParams = async (): Promise => { + return apiService.get( + SYNTHETICS_API_URLS.PARAMS, + undefined, + SyntheticsParamsReadonlyCodec + ); }; export const addGlobalParam = async ( paramRequest: SyntheticsParamRequest -): Promise => { - return apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest); -}; +): Promise => + apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest, SyntheticsParamsCodec); export const editGlobalParam = async ({ paramRequest, @@ -29,19 +34,17 @@ export const editGlobalParam = async ({ }: { id: string; paramRequest: SyntheticsParamRequest; -}): Promise => { - return apiService.put(SYNTHETICS_API_URLS.PARAMS, { - id, - ...paramRequest, - }); -}; +}): Promise => + apiService.put( + SYNTHETICS_API_URLS.PARAMS, + { + id, + ...paramRequest, + }, + SyntheticsParamsCodec + ); -export const deleteGlobalParams = async ({ - ids, -}: { - ids: string[]; -}): Promise => { - return apiService.delete(SYNTHETICS_API_URLS.PARAMS, { +export const deleteGlobalParams = async (ids: string[]): Promise => + apiService.delete(SYNTHETICS_API_URLS.PARAMS, { ids: JSON.stringify(ids), }); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts index 6555518b6c505..89b3a0b7e1904 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/index.ts @@ -6,18 +6,17 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SyntheticsParamSO } from '../../../../../common/runtime_types'; +import { SyntheticsParams } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError } from '..'; import { addNewGlobalParamAction, editGlobalParamAction, getGlobalParamAction } from './actions'; export interface GlobalParamsState { isLoading?: boolean; - listOfParams?: Array>; + listOfParams?: SyntheticsParams[]; addError: IHttpSerializedFetchError | null; editError: IHttpSerializedFetchError | null; isSaving?: boolean; - savedData?: SyntheticsParamSO; + savedData?: SyntheticsParams; } const initialState: GlobalParamsState = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts index b1eb68738bc50..65cf3ab97d070 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts @@ -13,8 +13,12 @@ import { createAsyncAction } from '../utils/actions'; export const toggleTestNowFlyoutAction = createAction('TOGGLE TEST NOW FLYOUT ACTION'); export const hideTestNowFlyoutAction = createAction('HIDE ALL TEST NOW FLYOUT ACTION'); +export interface TestNowPayload { + configId: string; + name: string; +} export const manualTestMonitorAction = createAsyncAction< - { configId: string; name: string }, + TestNowPayload, TestNowResponse | undefined >('TEST_NOW_MONITOR_ACTION'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts index 73f6ec9f76a22..53a99a651ee93 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts @@ -10,12 +10,14 @@ import { createReducer, PayloadAction } from '@reduxjs/toolkit'; import { WritableDraft } from 'immer/dist/types/types-external'; import { IHttpFetchError } from '@kbn/core-http-browser'; +import { ActionPayload } from '../utils/actions'; import { TestNowResponse } from '../../../../../common/types'; import { clearTestNowMonitorAction, hideTestNowFlyoutAction, manualTestMonitorAction, manualTestRunUpdateAction, + TestNowPayload, toggleTestNowFlyoutAction, } from './actions'; import { @@ -57,10 +59,7 @@ export const manualTestRunsReducer = createReducer(initialState, (builder) => { builder .addCase( String(manualTestMonitorAction.get), - ( - state: WritableDraft, - action: PayloadAction<{ configId: string; name: string }> - ) => { + (state: WritableDraft, action: PayloadAction) => { state = Object.values(state).reduce((acc, curr) => { acc[curr.configId] = { ...curr, @@ -98,9 +97,12 @@ export const manualTestRunsReducer = createReducer(initialState, (builder) => { ) .addCase( String(manualTestMonitorAction.fail), - (state: WritableDraft, action: PayloadAction) => { + ( + state: WritableDraft, + action: ActionPayload + ) => { const fetchError = action.payload as unknown as IHttpFetchError; - if (fetchError?.request.url) { + if (fetchError?.request?.url) { const { name, message } = fetchError; const [, errorMonitor] = @@ -117,10 +119,10 @@ export const manualTestRunsReducer = createReducer(initialState, (builder) => { }; } } - - if (action.payload.configId) { - state[action.payload.configId] = { - ...state[action.payload.configId], + const configId = action.payload.configId ?? action.payload.getPayload?.configId; + if (configId) { + state[configId] = { + ...state[configId], status: TestRunStatus.COMPLETED, errors: action.payload.errors, fetchError: undefined, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts index 2a8d13a0f6025..789728faf70d9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createAction } from '@reduxjs/toolkit'; +import { createAction, PayloadAction } from '@reduxjs/toolkit'; import type { IHttpSerializedFetchError } from './http_error'; export function createAsyncAction< @@ -31,3 +31,7 @@ function prepareForTimestamp(payload: Payload) { }, }; } + +export interface ActionPayload extends PayloadAction

{ + payload: P & { getPayload?: G }; +} diff --git a/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts b/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts index b73b353f708f2..9dead89809ee9 100644 --- a/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts +++ b/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts @@ -41,20 +41,7 @@ class ApiService { return ApiService.instance; } - public async get( - apiUrl: string, - params?: HttpFetchQuery, - decodeType?: any, - asResponse = false - ) { - const response = await this._http!.fetch({ - path: apiUrl, - query: params, - asResponse, - }); - - this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false }); - + private parseResponse(response: Awaited, apiUrl: string, decodeType?: any): T { if (decodeType) { const decoded = decodeType.decode(response); if (isRight(decoded)) { @@ -69,10 +56,26 @@ class ApiService { ); } } - return response; } + public async get( + apiUrl: string, + params?: HttpFetchQuery, + decodeType?: any, + asResponse = false + ) { + const response = await this._http!.fetch({ + path: apiUrl, + query: params, + asResponse, + }); + + this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false }); + + return this.parseResponse(response, apiUrl, decodeType); + } + public async post(apiUrl: string, data?: any, decodeType?: any, params?: HttpFetchQuery) { const response = await this._http!.post(apiUrl, { method: 'POST', @@ -82,18 +85,7 @@ class ApiService { this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false }); - if (decodeType) { - const decoded = decodeType.decode(response); - if (isRight(decoded)) { - return decoded.right as T; - } else { - // eslint-disable-next-line no-console - console.warn( - `API ${apiUrl} is not returning expected response, ${formatErrors(decoded.left)}` - ); - } - } - return response; + return this.parseResponse(response, apiUrl, decodeType); } public async put(apiUrl: string, data?: any, decodeType?: any) { @@ -102,18 +94,7 @@ class ApiService { body: JSON.stringify(data), }); - if (decodeType) { - const decoded = decodeType.decode(response); - if (isRight(decoded)) { - return decoded.right as T; - } else { - // eslint-disable-next-line no-console - console.warn( - `API ${apiUrl} is not returning expected response, ${formatErrors(decoded.left)}` - ); - } - } - return response; + return this.parseResponse(response, apiUrl, decodeType); } public async delete(apiUrl: string, params?: HttpFetchQuery) { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts index 8ff63923aba16..f675f10d6be92 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -316,9 +316,13 @@ const setupGettingStarted = (configId: string, routeContext: RouteContext) => { if (gettingStarted) { // ignore await, since we don't want to block the response - triggerTestNow(configId, routeContext).then(() => { - server.logger.debug(`Successfully triggered test for monitor: ${configId}`); - }); + triggerTestNow(configId, routeContext) + .then(() => { + server.logger.debug(`Successfully triggered test for monitor: ${configId}`); + }) + .catch((e) => { + server.logger.error(`Error triggering test for monitor: ${configId}: ${e}`); + }); } } catch (e) { server.logger.info(`Error triggering test for getting started monitor: ${configId}`); diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index 032a48695ec97..91a7a03429030 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -6,7 +6,6 @@ */ import { schema } from '@kbn/config-schema'; import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { SyntheticsServerSetup } from '../../types'; import { RouteContext, SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; @@ -69,19 +68,19 @@ export const deleteMonitor = async ({ routeContext: RouteContext; monitorId: string; }) => { - const { savedObjectsClient, server, syntheticsMonitorClient, request } = routeContext; + const { spaceId, savedObjectsClient, server, syntheticsMonitorClient, request } = routeContext; const { logger, telemetry, stackVersion } = server; const { monitor, monitorWithSecret } = await getMonitorToDelete( monitorId, savedObjectsClient, - server + server, + spaceId ); let deletePromise; try { - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; deletePromise = savedObjectsClient.delete(syntheticsMonitorType, monitorId); const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors( @@ -140,7 +139,8 @@ export const deleteMonitor = async ({ const getMonitorToDelete = async ( monitorId: string, soClient: SavedObjectsClientContract, - server: SyntheticsServerSetup + server: SyntheticsServerSetup, + spaceId: string ) => { const encryptedSOClient = server.encryptedSavedObjects.getClient(); @@ -148,7 +148,10 @@ const getMonitorToDelete = async ( const monitor = await encryptedSOClient.getDecryptedAsInternalUser( syntheticsMonitorType, - monitorId + monitorId, + { + namespace: spaceId, + } ); return { monitor: normalizeSecrets(monitor), monitorWithSecret: normalizeSecrets(monitor) }; } catch (e) { diff --git a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts b/x-pack/plugins/synthetics/server/routes/settings/add_param.ts index b27ddf27a88b3..875e4da8694e1 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/add_param.ts @@ -8,8 +8,13 @@ import { schema } from '@kbn/config-schema'; import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { IKibanaResponse } from '@kbn/core/server'; import { SyntheticsRestApiRouteFactory } from '../types'; -import { SyntheticsParamRequest, SyntheticsParamSO } from '../../../common/runtime_types'; +import { + SyntheticsParamRequest, + SyntheticsParams, + SyntheticsParamSOAttributes, +} from '../../../common/runtime_types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -26,7 +31,12 @@ export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ }), }, writeAccess: true, - handler: async ({ request, response, server, savedObjectsClient }): Promise => { + handler: async ({ + request, + response, + server, + savedObjectsClient, + }): Promise> => { try { const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { id: DEFAULT_SPACE_ID, @@ -34,14 +44,33 @@ export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ const { share_across_spaces: shareAcrossSpaces, ...data } = request.body as SyntheticsParamRequest; - const result = await savedObjectsClient.create(syntheticsParamType, data, { - initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId], + const { + attributes: { key, tags, description }, + id, + namespaces, + } = await savedObjectsClient.create>( + syntheticsParamType, + data, + { + initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId], + } + ); + return response.ok({ + body: { + id, + description, + key, + namespaces, + tags, + value: data.value, + }, }); - return { data: result }; } catch (error) { if (error.output?.statusCode === 404) { const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; - return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); + return response.notFound({ + body: { message: `Kibana space '${spaceId}' does not exist` }, + }); } throw error; diff --git a/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts b/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts index 7dd839a321088..dc529902b730f 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { IKibanaResponse } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { DeleteParamsResponse } from '../../../common/runtime_types'; export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'DELETE', @@ -19,7 +21,11 @@ export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => }), }, writeAccess: true, - handler: async ({ savedObjectsClient, request }): Promise => { + handler: async ({ + savedObjectsClient, + request, + response, + }): Promise> => { const { ids } = request.query as { ids: string }; const parsedIds = JSON.parse(ids) as string[]; @@ -27,7 +33,8 @@ export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => parsedIds.map((id) => ({ type: syntheticsParamType, id })), { force: true } ); - - return { data: result }; + return response.ok({ + body: result.statuses.map(({ id, success }) => ({ id, deleted: success })), + }); }, }); diff --git a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts b/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts index 0201f350249c6..b235d0b323c8b 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts @@ -6,9 +6,10 @@ */ import { schema } from '@kbn/config-schema'; +import { IKibanaResponse, SavedObject } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { SyntheticsRestApiRouteFactory } from '../types'; -import { SyntheticsParamRequest } from '../../../common/runtime_types'; +import { SyntheticsParamRequest, SyntheticsParams } from '../../../common/runtime_types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -26,7 +27,12 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ }), }, writeAccess: true, - handler: async ({ savedObjectsClient, request, response, server }): Promise => { + handler: async ({ + savedObjectsClient, + request, + response, + server, + }): Promise> => { try { const { id: _spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { id: DEFAULT_SPACE_ID, @@ -39,9 +45,18 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ id: string; }; - const result = await savedObjectsClient.update(syntheticsParamType, id, data); + const { value } = data; + const { + id: responseId, + attributes: { key, tags, description }, + namespaces, + } = (await savedObjectsClient.update( + syntheticsParamType, + id, + data + )) as SavedObject; - return { data: result }; + return response.ok({ body: { id: responseId, key, tags, description, namespaces, value } }); } catch (error) { if (error.output?.statusCode === 404) { const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; diff --git a/x-pack/plugins/synthetics/server/routes/settings/params.ts b/x-pack/plugins/synthetics/server/routes/settings/params.ts index 5622438ab47c0..789761c529e84 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/params.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/params.ts @@ -5,52 +5,66 @@ * 2.0. */ +import { IKibanaResponse } from '@kbn/core/server'; import { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { SyntheticsParams, SyntheticsParamsReadonly } from '../../../common/runtime_types'; -export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ +type SyntheticsParamsResponse = + | IKibanaResponse + | IKibanaResponse; +export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< + SyntheticsParamsResponse +> = () => ({ method: 'GET', path: SYNTHETICS_API_URLS.PARAMS, validate: {}, - handler: async ({ savedObjectsClient, request, response, server }): Promise => { + handler: async ({ savedObjectsClient, request, response, server, spaceId }) => { try { const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient(); - const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { - id: DEFAULT_SPACE_ID, - }; - const canSave = (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false; if (canSave) { const finder = - await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser({ - type: syntheticsParamType, - perPage: 1000, - namespaces: [spaceId], - }); + await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + type: syntheticsParamType, + perPage: 1000, + namespaces: [spaceId], + } + ); - const hits: SavedObjectsFindResult[] = []; + const hits: Array> = []; for await (const result of finder.find()) { hits.push(...result.saved_objects); } - return { data: hits }; + return response.ok({ + body: hits.map(({ id, attributes, namespaces }) => ({ + ...attributes, + id, + namespaces, + })), + }); } else { - const data = await savedObjectsClient.find({ + const data = await savedObjectsClient.find({ type: syntheticsParamType, perPage: 10000, }); - - return { data: data.saved_objects }; + return response.ok({ + body: data.saved_objects.map(({ id, attributes, namespaces }) => ({ + ...attributes, + namespaces, + id, + })), + }); } } catch (error) { if (error.output?.statusCode === 404) { - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); } diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts index 22fc903055eba..bf34e20039867 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts @@ -40,7 +40,10 @@ export const triggerTestNow = async ( const monitorWithSecrets = await encryptedClient.getDecryptedAsInternalUser( syntheticsMonitorType, - monitorId + monitorId, + { + namespace: spaceId, + } ); const normalizedMonitor = normalizeSecrets(monitorWithSecrets); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index a464e4ea0f971..ecfb10cc1632f 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -36,7 +36,7 @@ import { ServiceLocations, SyntheticsMonitorWithId, SyntheticsMonitorWithSecrets, - SyntheticsParamSO, + SyntheticsParams, ThrottlingOptions, } from '../../common/runtime_types'; import { getServiceLocations } from './get_service_locations'; @@ -599,7 +599,7 @@ export class SyntheticsService { const paramsBySpace: Record> = Object.create(null); const finder = - await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ + await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ type: syntheticsParamType, perPage: 1000, namespaces: spaceId ? [spaceId] : undefined, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d28554d63bbc3..9ab100fe729a5 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -5673,7 +5673,6 @@ "unifiedHistogram.lensTitle": "Modifier la visualisation", "unifiedHistogram.resetChartHeight": "Réinitialiser à la hauteur par défaut", "unifiedHistogram.showChart": "Afficher le graphique", - "unifiedHistogram.suggestionSelectorLabel": "Visualisation", "unifiedHistogram.suggestionSelectorPlaceholder": "Sélectionner la visualisation", "unifiedHistogram.timeIntervals": "Intervalles de temps", "unifiedHistogram.timeIntervalWithValueWarning": "Avertissement", @@ -18118,7 +18117,6 @@ "xpack.infra.analysisSetup.indicesSelectionIndexNotFound": "Aucun index ne correspond au modèle {index}", "xpack.infra.analysisSetup.indicesSelectionNoTimestampField": "Il manque un champ {field} obligatoire dans au moins un index correspondant à {index}.", "xpack.infra.analysisSetup.indicesSelectionTimestampNotValid": "Au moins un index correspondant à {index} comprend un champ appelé {field} sans le type correct.", - "xpack.infra.dataSearch.shardFailureErrorMessage": "Index {indexName} : {errorMessage}", "xpack.infra.deprecations.containerAdjustIndexing": "Ajustez votre indexation pour identifier les conteneurs Docker utilisant \"{field}\"", "xpack.infra.deprecations.deprecatedFieldConfigDescription": "La configuration de \"xpack.infra.sources.default.fields.{fieldKey}\" a été déclassée et sera retirée dans la version 8.0.0.", "xpack.infra.deprecations.deprecatedFieldConfigTitle": "\"{fieldKey}\" est déclassé.", @@ -18139,8 +18137,6 @@ "xpack.infra.kibanaMetrics.nodeDoesNotExistErrorMessage": "{nodeId} n'existe pas.", "xpack.infra.linkTo.hostWithIp.error": "Hôte avec l'adresse IP \"{hostIp}\" introuvable.", "xpack.infra.linkTo.hostWithIp.loading": "Chargement de l'hôte avec l'adresse IP \"{hostIp}\" en cours.", - "xpack.infra.logFlyout.flyoutSubTitle": "À partir de l'index {indexName}", - "xpack.infra.logFlyout.flyoutTitle": "Détails de l'entrée de log {logEntryId}", "xpack.infra.logs.alertDetails.chart.chartTitle": "Logs pour {criteria}", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "Lors de la définition d'une valeur \"regrouper par\", nous recommandons fortement d'utiliser le comparateur \"{comparator}\" pour votre seuil. Cela peut permettre d'améliorer considérablement les performances.", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "{actualCount, plural, one {la {actualCount} dernière entrée de log} many {les {actualCount} dernières entrées de log} other {les {actualCount} dernières entrées de log}} dans {duration} pour {groupName}. Alerte lorsque {comparator} {expectedCount}.", @@ -18158,22 +18154,11 @@ "xpack.infra.logs.analysis.mlUnavailableBody": "Pour en savoir plus, consultez {machineLearningAppLink}.", "xpack.infra.logs.common.invalidStateMessage": "Impossible de traiter l'état {stateValue}.", "xpack.infra.logs.customizeLogs.textSizeRadioGroup": "{textScale, select, small {Petite} medium {Moyenne} large {Large} other {{textScale}}}", - "xpack.infra.logs.extendTimeframeByDaysButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {jour} many {jours} other {jours}}", - "xpack.infra.logs.extendTimeframeByHoursButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {heure} many {heures} other {heures}}", - "xpack.infra.logs.extendTimeframeByMillisecondsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {milliseconde} many {millisecondes} other {millisecondes}}", - "xpack.infra.logs.extendTimeframeByMinutesButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {minute} many {minutes} other {minutes}}", - "xpack.infra.logs.extendTimeframeByMonthsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {mois} many {mois} other {mois}}", - "xpack.infra.logs.extendTimeframeBySecondsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {seconde} many {secondes} other {secondes}}", - "xpack.infra.logs.extendTimeframeByWeeksButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {semaine} many {semaines} other {semaines}}", - "xpack.infra.logs.extendTimeframeByYearsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {an} many {années} other {années}}", - "xpack.infra.logs.lastUpdate": "Dernière mise à jour {timestamp}", "xpack.infra.logs.logEntryCategories.manyCategoriesWarningReasonDescription": "Le rapport de catégories par document analysé est très élevé avec {categoriesDocumentRatio, number}.", "xpack.infra.logs.logEntryCategories.manyDeadCategoriesWarningReasonDescription": "Aucun nouveau message n'est attribué à {deadCategoriesRatio, number, percent} des catégories, car des catégories moins spécifiques les masquent.", "xpack.infra.logs.logEntryCategories.manyRareCategoriesWarningReasonDescription": "Les messages sont rarement attribués à {rareCategoriesRatio, number, percent} des catégories.", "xpack.infra.logs.logEntryCategories.truncatedPatternSegmentDescription": "{extraSegmentCount, plural, one {un autre segment} many {# autres segments} other {# autres segments}}", "xpack.infra.logs.searchResultTooltip": "{bucketCount, plural, one {# entrée mise en surbrillance} many {# entrées mises en surbrillance} other {# entrées mises en surbrillance}}", - "xpack.infra.logs.showingEntriesFromTimestamp": "Affichage des entrées à partir de {timestamp}", - "xpack.infra.logs.showingEntriesUntilTimestamp": "Affichage des entrées jusqu'à {timestamp}", "xpack.infra.logs.viewInContext.logsFromContainerTitle": "Les logs affichés proviennent du conteneur {container}", "xpack.infra.logs.viewInContext.logsFromFileTitle": "Les logs affichés proviennent du fichier {file} et de l'hôte {host}", "xpack.infra.logSourceConfiguration.invalidMessageFieldTypeErrorMessage": "Le champ {messageField} doit être un champ textuel.", @@ -18284,9 +18269,6 @@ "xpack.infra.chartSection.notEnoughDataPointsToRenderTitle": "Données insuffisantes", "xpack.infra.common.tabBetaBadgeLabel": "Version bêta", "xpack.infra.configureSourceActionLabel": "Modifier la configuration de la source", - "xpack.infra.dataSearch.abortedRequestErrorMessage": "La demande a été annulée.", - "xpack.infra.dataSearch.cancelButtonLabel": "Annuler la demande", - "xpack.infra.dataSearch.loadingErrorRetryButtonLabel": "Réessayer", "xpack.infra.deprecations.containerIdFieldName": "ID de conteneur", "xpack.infra.deprecations.containerIdFieldTitle": "Le champ de configuration source \"ID de conteneur\" est déclassé.", "xpack.infra.deprecations.hostnameFieldName": "nom d'hôte", @@ -18440,18 +18422,7 @@ "xpack.infra.legendControls.stepsLabel": "Nombre de couleurs", "xpack.infra.legendControls.switchLabel": "Calculer automatiquement la plage", "xpack.infra.legnedControls.boundRangeError": "La valeur minimale doit être inférieure à la valeur maximale", - "xpack.infra.lobs.logEntryActionsViewInContextButton": "Afficher en contexte", "xpack.infra.logAnomalies.logEntryExamplesMenuLabel": "Afficher les actions de l'entrée du log", - "xpack.infra.logEntryActionsMenu.apmActionLabel": "Afficher dans APM", - "xpack.infra.logEntryActionsMenu.buttonLabel": "Examiner", - "xpack.infra.logEntryActionsMenu.uptimeActionLabel": "Afficher le statut dans Uptime", - "xpack.infra.logEntryItemView.logEntryActionsMenuToolTip": "Afficher les actions de la ligne", - "xpack.infra.logFlyout.fieldColumnLabel": "Champ", - "xpack.infra.logFlyout.filterAriaLabel": "Filtre", - "xpack.infra.logFlyout.loadingErrorCalloutTitle": "Erreur lors de la recherche de l'entrée de log", - "xpack.infra.logFlyout.loadingMessage": "Recherche de l'entrée de log dans les partitions", - "xpack.infra.logFlyout.setFilterTooltip": "Afficher l'événement avec filtre", - "xpack.infra.logFlyout.valueColumnLabel": "Valeur", "xpack.infra.logs.alertDetails.chart.ratioTitle": "Ratio de Requête A à Requête B", "xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "Alerte démarrée", "xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "Alertes déclenchées", @@ -18570,9 +18541,6 @@ "xpack.infra.logs.customizeLogs.lineWrappingFormRowLabel": "Retour automatique à la ligne", "xpack.infra.logs.customizeLogs.textSizeFormRowLabel": "Taille du texte", "xpack.infra.logs.customizeLogs.wrapLongLinesSwitchLabel": "Formater les longues lignes", - "xpack.infra.logs.emptyView.checkForNewDataButtonLabel": "Rechercher de nouvelles données", - "xpack.infra.logs.emptyView.noLogMessageDescription": "Essayez d'ajuster votre filtre.", - "xpack.infra.logs.emptyView.noLogMessageTitle": "Il n'y a aucun message de log à afficher.", "xpack.infra.logs.highlights.clearHighlightTermsButtonLabel": "Effacer les termes à mettre en surbrillance", "xpack.infra.logs.highlights.goToNextHighlightButtonLabel": "Passer au surlignage suivant", "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "Passer au surlignage précédent", @@ -18582,10 +18550,7 @@ "xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "Catégories", "xpack.infra.logs.index.settingsTabTitle": "Paramètres", "xpack.infra.logs.index.streamTabTitle": "Flux", - "xpack.infra.logs.jumpToTailText": "Passer aux entrées les plus récentes", - "xpack.infra.logs.loadingNewEntriesText": "Chargement des nouvelles entrées", "xpack.infra.logs.logCategoriesTitle": "Catégories", - "xpack.infra.logs.logEntryActionsDetailsButton": "Afficher les détails", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlButtonLabel": "Analyse dans ML", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlTooltipDescription": "Analysez cette catégorie dans l'application ML.", "xpack.infra.logs.logEntryCategories.categoryColumnTitle": "Catégorie", @@ -18616,7 +18581,6 @@ "xpack.infra.logs.noDataConfig.beatsCard.title": "Ajouter une intégration au logging", "xpack.infra.logs.noDataConfig.solutionName": "Observabilité", "xpack.infra.logs.pluginTitle": "Logs", - "xpack.infra.logs.scrollableLogTextStreamView.loadingEntriesLabel": "Chargement des entrées", "xpack.infra.logs.search.nextButtonLabel": "Suivant", "xpack.infra.logs.search.previousButtonLabel": "Précédent", "xpack.infra.logs.search.searchInLogsAriaLabel": "rechercher", @@ -18626,10 +18590,6 @@ "xpack.infra.logs.settings.inlineLogViewCalloutTitle": "Vue de log en ligne utilisée", "xpack.infra.logs.startStreamingButtonLabel": "Diffuser en direct", "xpack.infra.logs.stopStreamingButtonLabel": "Arrêter la diffusion", - "xpack.infra.logs.stream.messageColumnTitle": "Message", - "xpack.infra.logs.stream.timestampColumnTitle": "Horodatage", - "xpack.infra.logs.streamingNewEntriesText": "Diffusion de nouvelles entrées", - "xpack.infra.logs.streamLive": "Diffuser en direct", "xpack.infra.logs.streamPageTitle": "Flux", "xpack.infra.logsHeaderAddDataButtonLabel": "Ajouter des données", "xpack.infra.logSourceConfiguration.childFormElementErrorMessage": "L'état d'au moins un champ du formulaire est non valide.", @@ -18656,8 +18616,6 @@ "xpack.infra.logSourceErrorPage.tryAgainButtonLabel": "Réessayer", "xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder": "Recherche d'entrées de log… (par ex. host.name:host-1)", "xpack.infra.logsPage.toolbar.logFilterErrorToastTitle": "Erreur de filtrage du log", - "xpack.infra.logStream.kqlErrorTitle": "Expression KQL non valide", - "xpack.infra.logStream.unknownErrorTitle": "Une erreur s'est produite", "xpack.infra.logStreamEmbeddable.description": "Ajoutez un tableau de logs de diffusion en direct.", "xpack.infra.logStreamEmbeddable.displayName": "Flux de log", "xpack.infra.logStreamEmbeddable.title": "Flux de log", @@ -19284,6 +19242,47 @@ "xpack.infra.waffle.unableToSelectMetricErrorTitle": "Impossible de sélectionner les options ou la valeur pour l'indicateur.", "xpack.infra.waffleTime.autoRefreshButtonLabel": "Actualisation automatique", "xpack.infra.waffleTime.stopRefreshingButtonLabel": "Arrêter l'actualisation", + "xpack.logsShared.dataSearch.shardFailureErrorMessage": "Index {indexName} : {errorMessage}", + "xpack.logsShared.logFlyout.flyoutSubTitle": "À partir de l'index {indexName}", + "xpack.logsShared.logFlyout.flyoutTitle": "Détails de l'entrée de log {logEntryId}", + "xpack.logsShared.logs.extendTimeframeByDaysButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {jour} many {jours} other {jours}}", + "xpack.logsShared.logs.extendTimeframeByHoursButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {heure} many {heures} other {heures}}", + "xpack.logsShared.logs.extendTimeframeByMillisecondsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {milliseconde} many {millisecondes} other {millisecondes}}", + "xpack.logsShared.logs.extendTimeframeByMinutesButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {minute} many {minutes} other {minutes}}", + "xpack.logsShared.logs.extendTimeframeByMonthsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {mois} many {mois} other {mois}}", + "xpack.logsShared.logs.extendTimeframeBySecondsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {seconde} many {secondes} other {secondes}}", + "xpack.logsShared.logs.extendTimeframeByWeeksButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {semaine} many {semaines} other {semaines}}", + "xpack.logsShared.logs.extendTimeframeByYearsButton": "Étendre le délai d'exécution de {amount, number} {amount, plural, one {an} many {années} other {années}}", + "xpack.logsShared.logs.lastUpdate": "Dernière mise à jour {timestamp}", + "xpack.logsShared.logs.showingEntriesFromTimestamp": "Affichage des entrées à partir de {timestamp}", + "xpack.logsShared.logs.showingEntriesUntilTimestamp": "Affichage des entrées jusqu'à {timestamp}", + "xpack.logsShared.dataSearch.abortedRequestErrorMessage": "La demande a été annulée.", + "xpack.logsShared.dataSearch.cancelButtonLabel": "Annuler la demande", + "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "Réessayer", + "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "Afficher en contexte", + "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "Afficher dans APM", + "xpack.logsShared.logEntryActionsMenu.buttonLabel": "Examiner", + "xpack.logsShared.logEntryActionsMenu.uptimeActionLabel": "Afficher le statut dans Uptime", + "xpack.logsShared.logEntryItemView.logEntryActionsMenuToolTip": "Afficher les actions de la ligne", + "xpack.logsShared.logFlyout.fieldColumnLabel": "Champ", + "xpack.logsShared.logFlyout.filterAriaLabel": "Filtre", + "xpack.logsShared.logFlyout.loadingErrorCalloutTitle": "Erreur lors de la recherche de l'entrée de log", + "xpack.logsShared.logFlyout.loadingMessage": "Recherche de l'entrée de log dans les partitions", + "xpack.logsShared.logFlyout.setFilterTooltip": "Afficher l'événement avec filtre", + "xpack.logsShared.logFlyout.valueColumnLabel": "Valeur", + "xpack.logsShared.logs.emptyView.checkForNewDataButtonLabel": "Rechercher de nouvelles données", + "xpack.logsShared.logs.emptyView.noLogMessageDescription": "Essayez d'ajuster votre filtre.", + "xpack.logsShared.logs.emptyView.noLogMessageTitle": "Il n'y a aucun message de log à afficher.", + "xpack.logsShared.logs.jumpToTailText": "Passer aux entrées les plus récentes", + "xpack.logsShared.logs.loadingNewEntriesText": "Chargement des nouvelles entrées", + "xpack.logsShared.logs.logEntryActionsDetailsButton": "Afficher les détails", + "xpack.logsShared.logs.scrollableLogTextStreamView.loadingEntriesLabel": "Chargement des entrées", + "xpack.logsShared.logs.stream.messageColumnTitle": "Message", + "xpack.logsShared.logs.stream.timestampColumnTitle": "Horodatage", + "xpack.logsShared.logs.streamingNewEntriesText": "Diffusion de nouvelles entrées", + "xpack.logsShared.logs.streamLive": "Diffuser en direct", + "xpack.logsShared.logStream.kqlErrorTitle": "Expression KQL non valide", + "xpack.logsShared.logStream.unknownErrorTitle": "Une erreur s'est produite", "xpack.ingestPipelines.app.deniedPrivilegeDescription": "Pour utiliser l'option Ingérer des pipelines, vous devez avoir {privilegesCount, plural, one {ce privilège de cluster} many {ces privilèges de cluster} other {ces privilèges de cluster}} : {missingPrivileges}.", "xpack.ingestPipelines.clone.loadSourcePipelineErrorTitle": "Impossible de charger {name}.", "xpack.ingestPipelines.createFromCsv.errorMessage": "{message}", @@ -20071,7 +20070,6 @@ "xpack.lens.functions.timeScale.dateColumnMissingMessage": "L'ID de colonne de date spécifié {columnId} n'existe pas.", "xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label} contient des valeurs de tableau. Le rendu de votre visualisation peut ne pas se présenter comme attendu.", "xpack.lens.indexPattern.addColumnAriaLabel": "Ajouter ou faire glisser un champ vers {groupLabel}", - "xpack.lens.indexPattern.addColumnAriaLabelClick": "Ajouter une annotation à {groupLabel}", "xpack.lens.indexPattern.annotationsDimensionEditorLabel": "Annotation {groupLabel}", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "{name} pour cette visualisation peut être approximatif en raison de la manière dont les données sont indexées. Essayez de trier par rareté plutôt que par nombre ascendant d’enregistrements. Pour en savoir plus sur cette limitation, {link}.", "xpack.lens.indexPattern.autoIntervalLabel": "Auto ({interval})", @@ -20306,7 +20304,6 @@ "xpack.lens.configPanel.selectVisualization": "Sélectionner une visualisation", "xpack.lens.configPanel.visualizationType": "Type de visualisation", "xpack.lens.configure.emptyConfig": "Ajouter ou glisser-déposer un champ", - "xpack.lens.configure.emptyConfigClick": "Ajouter une annotation", "xpack.lens.configure.invalidBottomReferenceLineDimension": "La ligne de référence est affectée à un axe qui n’existe plus ou qui n’est plus valide. Vous pouvez déplacer cette ligne de référence vers un autre axe disponible ou la supprimer.", "xpack.lens.configure.invalidConfigTooltip": "Configuration non valide.", "xpack.lens.configure.invalidConfigTooltipClick": "Cliquez pour en savoir plus.", @@ -37763,7 +37760,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "Ajouter un connecteur", "xpack.triggersActionsUI.sections.actionTypeForm.notifyWhenThrottleWarning": "Les intervalles d'action personnalisés ne peuvent pas être plus courts que l'intervalle de vérification de la règle", "xpack.triggersActionsUI.sections.actionTypeForm.summaryGroupTitle": "Résumé des alertes", - "xpack.triggersActionsUI.sections.actionTypeForm.warning.publicUrl": "server.publicBaseUrl n'est pas défini. Les actions utiliseront des URL relatives.", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutHeaderCompatibility": "Compatibilité :", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "Sélectionner un connecteur", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "Création de \"{connectorName}\" effectuée", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d8763e4030c78..c79922135bd8e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5674,7 +5674,6 @@ "unifiedHistogram.lensTitle": "ビジュアライゼーションを編集", "unifiedHistogram.resetChartHeight": "デフォルトの高さにリセット", "unifiedHistogram.showChart": "グラフを表示", - "unifiedHistogram.suggestionSelectorLabel": "ビジュアライゼーション", "unifiedHistogram.suggestionSelectorPlaceholder": "ビジュアライゼーションを選択", "unifiedHistogram.timeIntervals": "時間間隔", "unifiedHistogram.timeIntervalWithValueWarning": "警告", @@ -18117,7 +18116,6 @@ "xpack.infra.analysisSetup.indicesSelectionIndexNotFound": "インデックスがパターン{index}と一致しません", "xpack.infra.analysisSetup.indicesSelectionNoTimestampField": "{index}と一致する1つ以上のインデックスに、必須フィールド{field}がありません。", "xpack.infra.analysisSetup.indicesSelectionTimestampNotValid": "{index}と一致する1つ以上のインデックスに、正しい型がない{field}フィールドがあります。", - "xpack.infra.dataSearch.shardFailureErrorMessage": "インデックス{indexName}:{errorMessage}", "xpack.infra.deprecations.containerAdjustIndexing": "インデックスを調整し、\"{field}\"を使用してDockerコンテナーを特定", "xpack.infra.deprecations.deprecatedFieldConfigDescription": "「xpack.infra.sources.default.fields.{fieldKey}」の構成は廃止予定であり、8.0.0で削除されます。", "xpack.infra.deprecations.deprecatedFieldConfigTitle": "\"{fieldKey}\"は廃止予定です。", @@ -18138,8 +18136,6 @@ "xpack.infra.kibanaMetrics.nodeDoesNotExistErrorMessage": "{nodeId}は存在しません。", "xpack.infra.linkTo.hostWithIp.error": "IPアドレス「{hostIp}」でホストが見つかりません。", "xpack.infra.linkTo.hostWithIp.loading": "IPアドレス「{hostIp}」のホストを読み込み中です。", - "xpack.infra.logFlyout.flyoutSubTitle": "インデックス{indexName}から", - "xpack.infra.logFlyout.flyoutTitle": "ログエントリ{logEntryId}の詳細", "xpack.infra.logs.alertDetails.chart.chartTitle": "{criteria} のログ", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "「group by」を設定するときには、しきい値で\"{comparator}\"比較演算子を使用することを強くお勧めします。これにより、パフォーマンスを大きく改善できます。", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "{groupName}の過去{duration}の{actualCount, plural, other {{actualCount}件のログエントリ}}。{comparator} {expectedCount}のときにアラートを通知します。", @@ -18157,22 +18153,11 @@ "xpack.infra.logs.analysis.mlUnavailableBody": "詳細は {machineLearningAppLink} をご覧ください。", "xpack.infra.logs.common.invalidStateMessage": "状態{stateValue}を処理できません。", "xpack.infra.logs.customizeLogs.textSizeRadioGroup": "{textScale, select, small {小} medium {中} large {大} other {{textScale}}}", - "xpack.infra.logs.extendTimeframeByDaysButton": "タイムフレームを{amount, number}{amount, plural, other {日}}延長", - "xpack.infra.logs.extendTimeframeByHoursButton": "タイムフレームを{amount, number}{amount, plural, other {時間}}延長", - "xpack.infra.logs.extendTimeframeByMillisecondsButton": "タイムフレームを{amount, number}{amount, plural, other {ミリ秒}}延長", - "xpack.infra.logs.extendTimeframeByMinutesButton": "タイムフレームを{amount, number}{amount, plural, other {分}}延長", - "xpack.infra.logs.extendTimeframeByMonthsButton": "タイムフレームを{amount, number}{amount, plural, other {月}}延長", - "xpack.infra.logs.extendTimeframeBySecondsButton": "タイムフレームを{amount, number}{amount, plural, other {秒}}延長", - "xpack.infra.logs.extendTimeframeByWeeksButton": "タイムフレームを{amount, number}{amount, plural, other {週}}延長", - "xpack.infra.logs.extendTimeframeByYearsButton": "タイムフレームを{amount, number}{amount, plural, other {年}}延長", - "xpack.infra.logs.lastUpdate": "最終更新:{timestamp}", "xpack.infra.logs.logEntryCategories.manyCategoriesWarningReasonDescription": "分析されたドキュメントごとのカテゴリ比率が{categoriesDocumentRatio, number}で、非常に高い値です。", "xpack.infra.logs.logEntryCategories.manyDeadCategoriesWarningReasonDescription": "特定のカテゴリが少ないことで、目立たなくなるため、{deadCategoriesRatio, number, percent}のカテゴリには新しいメッセージが割り当てられません。", "xpack.infra.logs.logEntryCategories.manyRareCategoriesWarningReasonDescription": "{rareCategoriesRatio, number, percent}のカテゴリには、ほとんどメッセージが割り当てられません。", "xpack.infra.logs.logEntryCategories.truncatedPatternSegmentDescription": "{extraSegmentCount, plural, other {#個の追加のセグメント}}", "xpack.infra.logs.searchResultTooltip": "{bucketCount, plural, other {#個のハイライトされたエントリ}}", - "xpack.infra.logs.showingEntriesFromTimestamp": "{timestamp}以降のエントリーを表示中", - "xpack.infra.logs.showingEntriesUntilTimestamp": "{timestamp}までのエントリーを表示中", "xpack.infra.logs.viewInContext.logsFromContainerTitle": "表示されたログはコンテナー{container}から取得されました", "xpack.infra.logs.viewInContext.logsFromFileTitle": "表示されたログは、ファイル{file}およびホスト{host}から取得されました", "xpack.infra.logSourceConfiguration.invalidMessageFieldTypeErrorMessage": "{messageField}フィールドはテキストフィールドでなければなりません。", @@ -18283,9 +18268,6 @@ "xpack.infra.chartSection.notEnoughDataPointsToRenderTitle": "データが不十分です", "xpack.infra.common.tabBetaBadgeLabel": "ベータ", "xpack.infra.configureSourceActionLabel": "ソース構成を変更", - "xpack.infra.dataSearch.abortedRequestErrorMessage": "リクエストが中断されましたか。", - "xpack.infra.dataSearch.cancelButtonLabel": "リクエストのキャンセル", - "xpack.infra.dataSearch.loadingErrorRetryButtonLabel": "再試行", "xpack.infra.deprecations.containerIdFieldName": "コンテナーID", "xpack.infra.deprecations.containerIdFieldTitle": "ソース構成フィールド[コンテナーID]は廃止予定です。", "xpack.infra.deprecations.hostnameFieldName": "ホスト名", @@ -18439,18 +18421,7 @@ "xpack.infra.legendControls.stepsLabel": "色の数", "xpack.infra.legendControls.switchLabel": "自動計算範囲", "xpack.infra.legnedControls.boundRangeError": "最小値は最大値よりも小さくなければなりません", - "xpack.infra.lobs.logEntryActionsViewInContextButton": "コンテキストで表示", "xpack.infra.logAnomalies.logEntryExamplesMenuLabel": "ログエントリのアクションを表示", - "xpack.infra.logEntryActionsMenu.apmActionLabel": "APMで表示", - "xpack.infra.logEntryActionsMenu.buttonLabel": "調査", - "xpack.infra.logEntryActionsMenu.uptimeActionLabel": "監視ステータスを表示", - "xpack.infra.logEntryItemView.logEntryActionsMenuToolTip": "行のアクションを表示", - "xpack.infra.logFlyout.fieldColumnLabel": "フィールド", - "xpack.infra.logFlyout.filterAriaLabel": "フィルター", - "xpack.infra.logFlyout.loadingErrorCalloutTitle": "ログエントリの検索中のエラー", - "xpack.infra.logFlyout.loadingMessage": "シャードのログエントリを検索しています", - "xpack.infra.logFlyout.setFilterTooltip": "フィルターでイベントを表示", - "xpack.infra.logFlyout.valueColumnLabel": "値", "xpack.infra.logs.alertDetails.chart.ratioTitle": "クエリAとクエリBの比率", "xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "アラートが開始しました", "xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "アラートがトリガーされました", @@ -18569,9 +18540,6 @@ "xpack.infra.logs.customizeLogs.lineWrappingFormRowLabel": "改行", "xpack.infra.logs.customizeLogs.textSizeFormRowLabel": "テキストサイズ", "xpack.infra.logs.customizeLogs.wrapLongLinesSwitchLabel": "長い行を改行", - "xpack.infra.logs.emptyView.checkForNewDataButtonLabel": "新規データを確認", - "xpack.infra.logs.emptyView.noLogMessageDescription": "フィルターを調整してみてください。", - "xpack.infra.logs.emptyView.noLogMessageTitle": "表示するログメッセージがありません。", "xpack.infra.logs.highlights.clearHighlightTermsButtonLabel": "ハイライトする用語をクリア", "xpack.infra.logs.highlights.goToNextHighlightButtonLabel": "次のハイライトにスキップ", "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "前のハイライトにスキップ", @@ -18581,10 +18549,7 @@ "xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "カテゴリー", "xpack.infra.logs.index.settingsTabTitle": "設定", "xpack.infra.logs.index.streamTabTitle": "ストリーム", - "xpack.infra.logs.jumpToTailText": "最も新しいエントリーに移動", - "xpack.infra.logs.loadingNewEntriesText": "新しいエントリーを読み込み中", "xpack.infra.logs.logCategoriesTitle": "カテゴリー", - "xpack.infra.logs.logEntryActionsDetailsButton": "詳細を表示", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlButtonLabel": "ML で分析", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlTooltipDescription": "ML アプリでこのカテゴリーを分析します。", "xpack.infra.logs.logEntryCategories.categoryColumnTitle": "カテゴリー", @@ -18615,7 +18580,6 @@ "xpack.infra.logs.noDataConfig.beatsCard.title": "ロギング統合を追加", "xpack.infra.logs.noDataConfig.solutionName": "Observability", "xpack.infra.logs.pluginTitle": "ログ", - "xpack.infra.logs.scrollableLogTextStreamView.loadingEntriesLabel": "エントリーを読み込み中", "xpack.infra.logs.search.nextButtonLabel": "次へ", "xpack.infra.logs.search.previousButtonLabel": "前へ", "xpack.infra.logs.search.searchInLogsAriaLabel": "検索", @@ -18625,10 +18589,6 @@ "xpack.infra.logs.settings.inlineLogViewCalloutTitle": "使用中のインラインログビュー", "xpack.infra.logs.startStreamingButtonLabel": "ライブストリーム", "xpack.infra.logs.stopStreamingButtonLabel": "ストリーム停止", - "xpack.infra.logs.stream.messageColumnTitle": "メッセージ", - "xpack.infra.logs.stream.timestampColumnTitle": "タイムスタンプ", - "xpack.infra.logs.streamingNewEntriesText": "新しいエントリーをストリーム中", - "xpack.infra.logs.streamLive": "ライブストリーム", "xpack.infra.logs.streamPageTitle": "ストリーム", "xpack.infra.logsHeaderAddDataButtonLabel": "データの追加", "xpack.infra.logSourceConfiguration.childFormElementErrorMessage": "1つ以上のフォームフィールドが無効な状態です。", @@ -18655,8 +18615,6 @@ "xpack.infra.logSourceErrorPage.tryAgainButtonLabel": "再試行", "xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder": "ログエントリーを検索中…(例:host.name:host-1)", "xpack.infra.logsPage.toolbar.logFilterErrorToastTitle": "ログフィルターエラー", - "xpack.infra.logStream.kqlErrorTitle": "無効なKQL式", - "xpack.infra.logStream.unknownErrorTitle": "エラーが発生しました", "xpack.infra.logStreamEmbeddable.description": "ライブストリーミングログのテーブルを追加します。", "xpack.infra.logStreamEmbeddable.displayName": "ログストリーム", "xpack.infra.logStreamEmbeddable.title": "ログストリーム", @@ -19283,6 +19241,47 @@ "xpack.infra.waffle.unableToSelectMetricErrorTitle": "メトリックのオプションまたは値を選択できません。", "xpack.infra.waffleTime.autoRefreshButtonLabel": "自動更新", "xpack.infra.waffleTime.stopRefreshingButtonLabel": "更新中止", + "xpack.logsShared.dataSearch.shardFailureErrorMessage": "インデックス{indexName}:{errorMessage}", + "xpack.logsShared.logFlyout.flyoutSubTitle": "インデックス{indexName}から", + "xpack.logsShared.logFlyout.flyoutTitle": "ログエントリ{logEntryId}の詳細", + "xpack.logsShared.logs.extendTimeframeByDaysButton": "タイムフレームを{amount, number}{amount, plural, other {日}}延長", + "xpack.logsShared.logs.extendTimeframeByHoursButton": "タイムフレームを{amount, number}{amount, plural, other {時間}}延長", + "xpack.logsShared.logs.extendTimeframeByMillisecondsButton": "タイムフレームを{amount, number}{amount, plural, other {ミリ秒}}延長", + "xpack.logsShared.logs.extendTimeframeByMinutesButton": "タイムフレームを{amount, number}{amount, plural, other {分}}延長", + "xpack.logsShared.logs.extendTimeframeByMonthsButton": "タイムフレームを{amount, number}{amount, plural, other {月}}延長", + "xpack.logsShared.logs.extendTimeframeBySecondsButton": "タイムフレームを{amount, number}{amount, plural, other {秒}}延長", + "xpack.logsShared.logs.extendTimeframeByWeeksButton": "タイムフレームを{amount, number}{amount, plural, other {週}}延長", + "xpack.logsShared.logs.extendTimeframeByYearsButton": "タイムフレームを{amount, number}{amount, plural, other {年}}延長", + "xpack.logsShared.logs.lastUpdate": "最終更新:{timestamp}", + "xpack.logsShared.logs.showingEntriesFromTimestamp": "{timestamp}以降のエントリーを表示中", + "xpack.logsShared.logs.showingEntriesUntilTimestamp": "{timestamp}までのエントリーを表示中", + "xpack.logsShared.dataSearch.abortedRequestErrorMessage": "リクエストが中断されましたか。", + "xpack.logsShared.dataSearch.cancelButtonLabel": "リクエストのキャンセル", + "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "再試行", + "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "コンテキストで表示", + "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "APMで表示", + "xpack.logsShared.logEntryActionsMenu.buttonLabel": "調査", + "xpack.logsShared.logEntryActionsMenu.uptimeActionLabel": "監視ステータスを表示", + "xpack.logsShared.logEntryItemView.logEntryActionsMenuToolTip": "行のアクションを表示", + "xpack.logsShared.logFlyout.fieldColumnLabel": "フィールド", + "xpack.logsShared.logFlyout.filterAriaLabel": "フィルター", + "xpack.logsShared.logFlyout.loadingErrorCalloutTitle": "ログエントリの検索中のエラー", + "xpack.logsShared.logFlyout.loadingMessage": "シャードのログエントリを検索しています", + "xpack.logsShared.logFlyout.setFilterTooltip": "フィルターでイベントを表示", + "xpack.logsShared.logFlyout.valueColumnLabel": "値", + "xpack.logsShared.logs.emptyView.checkForNewDataButtonLabel": "新規データを確認", + "xpack.logsShared.logs.emptyView.noLogMessageDescription": "フィルターを調整してみてください。", + "xpack.logsShared.logs.emptyView.noLogMessageTitle": "表示するログメッセージがありません。", + "xpack.logsShared.logs.jumpToTailText": "最も新しいエントリーに移動", + "xpack.logsShared.logs.loadingNewEntriesText": "新しいエントリーを読み込み中", + "xpack.logsShared.logs.logEntryActionsDetailsButton": "詳細を表示", + "xpack.logsShared.logs.scrollableLogTextStreamView.loadingEntriesLabel": "エントリーを読み込み中", + "xpack.logsShared.logs.stream.messageColumnTitle": "メッセージ", + "xpack.logsShared.logs.stream.timestampColumnTitle": "タイムスタンプ", + "xpack.logsShared.logs.streamingNewEntriesText": "新しいエントリーをストリーム中", + "xpack.logsShared.logs.streamLive": "ライブストリーム", + "xpack.logsShared.logStream.kqlErrorTitle": "無効なKQL式", + "xpack.logsShared.logStream.unknownErrorTitle": "エラーが発生しました", "xpack.ingestPipelines.app.deniedPrivilegeDescription": "インジェストパイプラインを使用するには、{privilegesCount, plural, other {これらのクラスター権限}}が必要です:{missingPrivileges}。", "xpack.ingestPipelines.clone.loadSourcePipelineErrorTitle": "{name}を読み込めません。", "xpack.ingestPipelines.createFromCsv.errorMessage": "{message}", @@ -20070,7 +20069,6 @@ "xpack.lens.functions.timeScale.dateColumnMissingMessage": "指定したdateColumnId {columnId}は存在しません。", "xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label}には配列値が含まれます。可視化が想定通りに表示されない場合があります。", "xpack.lens.indexPattern.addColumnAriaLabel": "フィールドを追加するか、{groupLabel}にドラッグアンドドロップします", - "xpack.lens.indexPattern.addColumnAriaLabelClick": "注釈を{groupLabel}に追加", "xpack.lens.indexPattern.annotationsDimensionEditorLabel": "{groupLabel}注釈", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "データのインデックス方法のため、このビジュアライゼーションの{name}は近似される場合があります。レコード数の昇順ではなく希少性で並べ替えてください。この制限の詳細については、{link}。", "xpack.lens.indexPattern.autoIntervalLabel": "自動({interval})", @@ -20306,7 +20304,6 @@ "xpack.lens.configPanel.selectVisualization": "ビジュアライゼーションを選択してください", "xpack.lens.configPanel.visualizationType": "ビジュアライゼーションタイプ", "xpack.lens.configure.emptyConfig": "フィールドを追加するか、ドラッグアンドドロップします", - "xpack.lens.configure.emptyConfigClick": "注釈の追加", "xpack.lens.configure.invalidBottomReferenceLineDimension": "この基準線は存在しないか有効ではない軸に割り当てられています。この基準線を別の使用可能な軸に移動するか、削除することができます。", "xpack.lens.configure.invalidConfigTooltip": "無効な構成です。", "xpack.lens.configure.invalidConfigTooltipClick": "詳細はクリックしてください。", @@ -37737,7 +37734,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "コネクターの追加", "xpack.triggersActionsUI.sections.actionTypeForm.notifyWhenThrottleWarning": "カスタムアクション間隔をルールのチェック間隔よりも短くすることはできません", "xpack.triggersActionsUI.sections.actionTypeForm.summaryGroupTitle": "アラートの概要", - "xpack.triggersActionsUI.sections.actionTypeForm.warning.publicUrl": "server.publicBaseUrlが設定されていません。アクションは相対URLを使用します。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutHeaderCompatibility": "互換性:", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "コネクターを選択", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 59d89ca314551..163f3c66e1448 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5673,7 +5673,6 @@ "unifiedHistogram.lensTitle": "编辑可视化", "unifiedHistogram.resetChartHeight": "重置为默认高度", "unifiedHistogram.showChart": "显示图表", - "unifiedHistogram.suggestionSelectorLabel": "可视化", "unifiedHistogram.suggestionSelectorPlaceholder": "选择可视化", "unifiedHistogram.timeIntervals": "时间间隔", "unifiedHistogram.timeIntervalWithValueWarning": "警告", @@ -18117,7 +18116,6 @@ "xpack.infra.analysisSetup.indicesSelectionIndexNotFound": "没有索引匹配模式 {index}", "xpack.infra.analysisSetup.indicesSelectionNoTimestampField": "匹配 {index} 的索引至少有一个缺少必需字段 {field}。", "xpack.infra.analysisSetup.indicesSelectionTimestampNotValid": "匹配 {index} 的索引至少有一个具有称作 {field} 且类型不正确的字段。", - "xpack.infra.dataSearch.shardFailureErrorMessage": "索引 {indexName}:{errorMessage}", "xpack.infra.deprecations.containerAdjustIndexing": "调整索引以使用“{field}”标识 Docker 容器", "xpack.infra.deprecations.deprecatedFieldConfigDescription": "配置“xpack.infra.sources.default.fields.{fieldKey}”已过时,将在 8.0.0 中移除。", "xpack.infra.deprecations.deprecatedFieldConfigTitle": "“{fieldKey}”已过时。", @@ -18138,8 +18136,6 @@ "xpack.infra.kibanaMetrics.nodeDoesNotExistErrorMessage": "{nodeId} 不存在。", "xpack.infra.linkTo.hostWithIp.error": "未找到 IP 地址为“{hostIp}”的主机。", "xpack.infra.linkTo.hostWithIp.loading": "正在加载 IP 地址为“{hostIp}”的主机。", - "xpack.infra.logFlyout.flyoutSubTitle": "从索引 {indexName}", - "xpack.infra.logFlyout.flyoutTitle": "日志条目 {logEntryId} 的详细信息", "xpack.infra.logs.alertDetails.chart.chartTitle": "{criteria} 的日志", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "设置“分组依据”时,强烈建议将“{comparator}”比较符用于阈值。这会使性能有较大提升。", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "对于 {groupName},过去 {duration}中有 {actualCount, plural, other {{actualCount} 个日志条目}}。{comparator} {expectedCount} 时告警。", @@ -18157,22 +18153,11 @@ "xpack.infra.logs.analysis.mlUnavailableBody": "查看 {machineLearningAppLink}以了解更多信息。", "xpack.infra.logs.common.invalidStateMessage": "无法处理状态 {stateValue}。", "xpack.infra.logs.customizeLogs.textSizeRadioGroup": "{textScale, select, small {小} medium {中} large {大} other {{textScale}}}", - "xpack.infra.logs.extendTimeframeByDaysButton": "将时间范围延伸 {amount, number} {amount, plural, other {天}}", - "xpack.infra.logs.extendTimeframeByHoursButton": "将时间范围延伸 {amount, number} {amount, plural, other {小时}}", - "xpack.infra.logs.extendTimeframeByMillisecondsButton": "将时间范围延伸 {amount, number} {amount, plural, other {毫秒}}", - "xpack.infra.logs.extendTimeframeByMinutesButton": "将时间范围延伸 {amount, number} {amount, plural, other {分钟}}", - "xpack.infra.logs.extendTimeframeByMonthsButton": "将时间范围延伸 {amount, number} 个{amount, plural, other {月}}", - "xpack.infra.logs.extendTimeframeBySecondsButton": "将时间范围延伸 {amount, number} {amount, plural, other {秒}}", - "xpack.infra.logs.extendTimeframeByWeeksButton": "将时间范围延伸 {amount, number} {amount, plural, other {周}}", - "xpack.infra.logs.extendTimeframeByYearsButton": "将时间范围延伸 {amount, number} {amount, plural, other {年}}", - "xpack.infra.logs.lastUpdate": "上次更新时间 {timestamp}", "xpack.infra.logs.logEntryCategories.manyCategoriesWarningReasonDescription": "每个分析文档的类别比率非常高,达到 {categoriesDocumentRatio, number}。", "xpack.infra.logs.logEntryCategories.manyDeadCategoriesWarningReasonDescription": "不会为 {deadCategoriesRatio, number, percent} 的类别分配新消息,因为较为笼统的类别遮蔽了它们。", "xpack.infra.logs.logEntryCategories.manyRareCategoriesWarningReasonDescription": "仅很少的时候为 {rareCategoriesRatio, number, percent} 的类别分配消息。", "xpack.infra.logs.logEntryCategories.truncatedPatternSegmentDescription": "{extraSegmentCount, plural, other {另 # 个分段}}", "xpack.infra.logs.searchResultTooltip": "{bucketCount, plural, other {# 个高亮条目}}", - "xpack.infra.logs.showingEntriesFromTimestamp": "正在显示自 {timestamp} 起的条目", - "xpack.infra.logs.showingEntriesUntilTimestamp": "正在显示截止于 {timestamp} 的条目", "xpack.infra.logs.viewInContext.logsFromContainerTitle": "显示的日志来自容器 {container}", "xpack.infra.logs.viewInContext.logsFromFileTitle": "显示的日志来自文件 {file} 和主机 {host}", "xpack.infra.logSourceConfiguration.invalidMessageFieldTypeErrorMessage": "{messageField} 字段必须是文本字段。", @@ -18283,9 +18268,6 @@ "xpack.infra.chartSection.notEnoughDataPointsToRenderTitle": "没有足够的数据", "xpack.infra.common.tabBetaBadgeLabel": "公测版", "xpack.infra.configureSourceActionLabel": "更改源配置", - "xpack.infra.dataSearch.abortedRequestErrorMessage": "请求已中止。", - "xpack.infra.dataSearch.cancelButtonLabel": "取消请求", - "xpack.infra.dataSearch.loadingErrorRetryButtonLabel": "重试", "xpack.infra.deprecations.containerIdFieldName": "容器 ID", "xpack.infra.deprecations.containerIdFieldTitle": "源配置字段“容器 ID”已过时。", "xpack.infra.deprecations.hostnameFieldName": "主机名", @@ -18439,18 +18421,7 @@ "xpack.infra.legendControls.stepsLabel": "颜色个数", "xpack.infra.legendControls.switchLabel": "自动计算范围", "xpack.infra.legnedControls.boundRangeError": "最小值必须小于最大值", - "xpack.infra.lobs.logEntryActionsViewInContextButton": "在上下文中查看", "xpack.infra.logAnomalies.logEntryExamplesMenuLabel": "查看日志条目的操作", - "xpack.infra.logEntryActionsMenu.apmActionLabel": "在 APM 中查看", - "xpack.infra.logEntryActionsMenu.buttonLabel": "调查", - "xpack.infra.logEntryActionsMenu.uptimeActionLabel": "在Uptime 中查看状态", - "xpack.infra.logEntryItemView.logEntryActionsMenuToolTip": "查看适用于以下行的操作:", - "xpack.infra.logFlyout.fieldColumnLabel": "字段", - "xpack.infra.logFlyout.filterAriaLabel": "筛选", - "xpack.infra.logFlyout.loadingErrorCalloutTitle": "搜索日志条目时出错", - "xpack.infra.logFlyout.loadingMessage": "正在分片中搜索日志条目", - "xpack.infra.logFlyout.setFilterTooltip": "使用筛选查看事件", - "xpack.infra.logFlyout.valueColumnLabel": "值", "xpack.infra.logs.alertDetails.chart.ratioTitle": "查询 A 到查询 B 的比率", "xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "已启动告警", "xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "已触发告警", @@ -18569,9 +18540,6 @@ "xpack.infra.logs.customizeLogs.lineWrappingFormRowLabel": "换行", "xpack.infra.logs.customizeLogs.textSizeFormRowLabel": "文本大小", "xpack.infra.logs.customizeLogs.wrapLongLinesSwitchLabel": "长行换行", - "xpack.infra.logs.emptyView.checkForNewDataButtonLabel": "检查新数据", - "xpack.infra.logs.emptyView.noLogMessageDescription": "尝试调整您的筛选。", - "xpack.infra.logs.emptyView.noLogMessageTitle": "没有可显示的日志消息。", "xpack.infra.logs.highlights.clearHighlightTermsButtonLabel": "清除要突出显示的词", "xpack.infra.logs.highlights.goToNextHighlightButtonLabel": "跳转到下一高亮条目", "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "跳转到上一高亮条目", @@ -18581,10 +18549,7 @@ "xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "类别", "xpack.infra.logs.index.settingsTabTitle": "设置", "xpack.infra.logs.index.streamTabTitle": "流式传输", - "xpack.infra.logs.jumpToTailText": "跳到最近的条目", - "xpack.infra.logs.loadingNewEntriesText": "正在加载新条目", "xpack.infra.logs.logCategoriesTitle": "类别", - "xpack.infra.logs.logEntryActionsDetailsButton": "查看详情", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlButtonLabel": "在 ML 中分析", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlTooltipDescription": "在 ML 应用中分析此类别。", "xpack.infra.logs.logEntryCategories.categoryColumnTitle": "类别", @@ -18615,7 +18580,6 @@ "xpack.infra.logs.noDataConfig.beatsCard.title": "添加日志记录集成", "xpack.infra.logs.noDataConfig.solutionName": "Observability", "xpack.infra.logs.pluginTitle": "日志", - "xpack.infra.logs.scrollableLogTextStreamView.loadingEntriesLabel": "正在加载条目", "xpack.infra.logs.search.nextButtonLabel": "下一页", "xpack.infra.logs.search.previousButtonLabel": "上一页", "xpack.infra.logs.search.searchInLogsAriaLabel": "搜索", @@ -18625,10 +18589,6 @@ "xpack.infra.logs.settings.inlineLogViewCalloutTitle": "正使用内联日志视图", "xpack.infra.logs.startStreamingButtonLabel": "实时流式传输", "xpack.infra.logs.stopStreamingButtonLabel": "停止流式传输", - "xpack.infra.logs.stream.messageColumnTitle": "消息", - "xpack.infra.logs.stream.timestampColumnTitle": "时间戳", - "xpack.infra.logs.streamingNewEntriesText": "正在流式传输新条目", - "xpack.infra.logs.streamLive": "实时流式传输", "xpack.infra.logs.streamPageTitle": "流式传输", "xpack.infra.logsHeaderAddDataButtonLabel": "添加数据", "xpack.infra.logSourceConfiguration.childFormElementErrorMessage": "至少一个表单字段处于无效状态。", @@ -18655,8 +18615,6 @@ "xpack.infra.logSourceErrorPage.tryAgainButtonLabel": "重试", "xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder": "搜索日志条目……(例如 host.name:host-1)", "xpack.infra.logsPage.toolbar.logFilterErrorToastTitle": "日志筛选错误", - "xpack.infra.logStream.kqlErrorTitle": "KQL 表达式无效", - "xpack.infra.logStream.unknownErrorTitle": "发生错误", "xpack.infra.logStreamEmbeddable.description": "添加实时流式传输日志的表。", "xpack.infra.logStreamEmbeddable.displayName": "日志流", "xpack.infra.logStreamEmbeddable.title": "日志流", @@ -19283,6 +19241,47 @@ "xpack.infra.waffle.unableToSelectMetricErrorTitle": "无法选择指标选项或指标值。", "xpack.infra.waffleTime.autoRefreshButtonLabel": "自动刷新", "xpack.infra.waffleTime.stopRefreshingButtonLabel": "停止刷新", + "xpack.logsShared.dataSearch.shardFailureErrorMessage": "索引 {indexName}:{errorMessage}", + "xpack.logsShared.logFlyout.flyoutSubTitle": "从索引 {indexName}", + "xpack.logsShared.logFlyout.flyoutTitle": "日志条目 {logEntryId} 的详细信息", + "xpack.logsShared.logs.extendTimeframeByDaysButton": "将时间范围延伸 {amount, number} {amount, plural, other {天}}", + "xpack.logsShared.logs.extendTimeframeByHoursButton": "将时间范围延伸 {amount, number} {amount, plural, other {小时}}", + "xpack.logsShared.logs.extendTimeframeByMillisecondsButton": "将时间范围延伸 {amount, number} {amount, plural, other {毫秒}}", + "xpack.logsShared.logs.extendTimeframeByMinutesButton": "将时间范围延伸 {amount, number} {amount, plural, other {分钟}}", + "xpack.logsShared.logs.extendTimeframeByMonthsButton": "将时间范围延伸 {amount, number} 个{amount, plural, other {月}}", + "xpack.logsShared.logs.extendTimeframeBySecondsButton": "将时间范围延伸 {amount, number} {amount, plural, other {秒}}", + "xpack.logsShared.logs.extendTimeframeByWeeksButton": "将时间范围延伸 {amount, number} {amount, plural, other {周}}", + "xpack.logsShared.logs.extendTimeframeByYearsButton": "将时间范围延伸 {amount, number} {amount, plural, other {年}}", + "xpack.logsShared.logs.lastUpdate": "上次更新时间 {timestamp}", + "xpack.logsShared.logs.showingEntriesFromTimestamp": "正在显示自 {timestamp} 起的条目", + "xpack.logsShared.logs.showingEntriesUntilTimestamp": "正在显示截止于 {timestamp} 的条目", + "xpack.logsShared.dataSearch.abortedRequestErrorMessage": "请求已中止。", + "xpack.logsShared.dataSearch.cancelButtonLabel": "取消请求", + "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "重试", + "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "在上下文中查看", + "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "在 APM 中查看", + "xpack.logsShared.logEntryActionsMenu.buttonLabel": "调查", + "xpack.logsShared.logEntryActionsMenu.uptimeActionLabel": "在Uptime 中查看状态", + "xpack.logsShared.logEntryItemView.logEntryActionsMenuToolTip": "查看适用于以下行的操作:", + "xpack.logsShared.logFlyout.fieldColumnLabel": "字段", + "xpack.logsShared.logFlyout.filterAriaLabel": "筛选", + "xpack.logsShared.logFlyout.loadingErrorCalloutTitle": "搜索日志条目时出错", + "xpack.logsShared.logFlyout.loadingMessage": "正在分片中搜索日志条目", + "xpack.logsShared.logFlyout.setFilterTooltip": "使用筛选查看事件", + "xpack.logsShared.logFlyout.valueColumnLabel": "值", + "xpack.logsShared.logs.emptyView.checkForNewDataButtonLabel": "检查新数据", + "xpack.logsShared.logs.emptyView.noLogMessageDescription": "尝试调整您的筛选。", + "xpack.logsShared.logs.emptyView.noLogMessageTitle": "没有可显示的日志消息。", + "xpack.logsShared.logs.jumpToTailText": "跳到最近的条目", + "xpack.logsShared.logs.loadingNewEntriesText": "正在加载新条目", + "xpack.logsShared.logs.logEntryActionsDetailsButton": "查看详情", + "xpack.logsShared.logs.scrollableLogTextStreamView.loadingEntriesLabel": "正在加载条目", + "xpack.logsShared.logs.stream.messageColumnTitle": "消息", + "xpack.logsShared.logs.stream.timestampColumnTitle": "时间戳", + "xpack.logsShared.logs.streamingNewEntriesText": "正在流式传输新条目", + "xpack.logsShared.logs.streamLive": "实时流式传输", + "xpack.logsShared.logStream.kqlErrorTitle": "KQL 表达式无效", + "xpack.logsShared.logStream.unknownErrorTitle": "发生错误", "xpack.ingestPipelines.app.deniedPrivilegeDescription": "要使用采集管道,您必须具有{privilegesCount, plural, other {以下集群权限}}:{missingPrivileges}。", "xpack.ingestPipelines.clone.loadSourcePipelineErrorTitle": "无法加载 {name}。", "xpack.ingestPipelines.createFromCsv.errorMessage": "{message}", @@ -20070,7 +20069,6 @@ "xpack.lens.functions.timeScale.dateColumnMissingMessage": "指定的 dateColumnId {columnId} 不存在。", "xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label} 包含数组值。您的可视化可能无法正常渲染。", "xpack.lens.indexPattern.addColumnAriaLabel": "将字段添加或拖放到 {groupLabel}", - "xpack.lens.indexPattern.addColumnAriaLabelClick": "添加标注到 {groupLabel}", "xpack.lens.indexPattern.annotationsDimensionEditorLabel": "{groupLabel} 标注", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "由于数据的索引方式,此可视化的 {name} 可能为近似值。尝试按稀有度排序,而不是采用升序记录计数。有关此限制的详情,{link}。", "xpack.lens.indexPattern.autoIntervalLabel": "自动 ({interval})", @@ -20306,7 +20304,6 @@ "xpack.lens.configPanel.selectVisualization": "选择可视化", "xpack.lens.configPanel.visualizationType": "可视化类型", "xpack.lens.configure.emptyConfig": "添加或拖放字段", - "xpack.lens.configure.emptyConfigClick": "添加标注", "xpack.lens.configure.invalidBottomReferenceLineDimension": "此参考线分配给了不再存在或不再有效的轴。您可以将此参考线移到其他可用的轴,或将其移除。", "xpack.lens.configure.invalidConfigTooltip": "配置无效。", "xpack.lens.configure.invalidConfigTooltipClick": "单击了解更多详情。", @@ -37731,7 +37728,6 @@ "xpack.triggersActionsUI.sections.actionTypeForm.addNewConnectorEmptyButton": "添加连接器", "xpack.triggersActionsUI.sections.actionTypeForm.notifyWhenThrottleWarning": "定制操作时间间隔不能短于规则的检查时间间隔", "xpack.triggersActionsUI.sections.actionTypeForm.summaryGroupTitle": "告警的摘要", - "xpack.triggersActionsUI.sections.actionTypeForm.warning.publicUrl": "未设置 server.publicBaseUrl。操作将使用相对 URL。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutHeaderCompatibility": "兼容性:", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "选择连接器", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.test.ts index 9de5749fbc2b3..fc0469c56cf50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.test.ts @@ -22,7 +22,8 @@ describe('validateParamsForWarnings', () => { ]; test('returns warnings when publicUrl is not set and there are publicUrl variables used', () => { - const warning = 'server.publicBaseUrl is not set. Actions will use relative URLs.'; + const warning = + 'server.publicBaseUrl is not set. Generated URLs will be either relative or empty.'; expect( validateParamsForWarnings('Test for {{context.url}}', undefined, actionVariables) ).toEqual(warning); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts index d361d6f739cdc..e0f552f6bb0f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts @@ -11,9 +11,10 @@ import { ActionVariable, RuleActionParam } from '@kbn/alerting-plugin/common'; import Mustache from 'mustache'; const publicUrlWarning = i18n.translate( - 'xpack.triggersActionsUI.sections.actionTypeForm.warning.publicUrl', + 'xpack.triggersActionsUI.sections.actionTypeForm.warning.publicBaseUrl', { - defaultMessage: 'server.publicBaseUrl is not set. Actions will use relative URLs.', + defaultMessage: + 'server.publicBaseUrl is not set. Generated URLs will be either relative or empty.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx index 33e1a59ac0280..755231033cc9c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx @@ -707,7 +707,8 @@ describe('AlertsTableState', () => { }); }); - describe('field browser', () => { + // FLAKY: https://github.com/elastic/kibana/issues/150790 + describe.skip('field browser', () => { const browserFields: BrowserFields = { kibana: { fields: { diff --git a/x-pack/plugins/upgrade_assistant/kibana.jsonc b/x-pack/plugins/upgrade_assistant/kibana.jsonc index 3f04775f4ca10..ff3584ef714bf 100644 --- a/x-pack/plugins/upgrade_assistant/kibana.jsonc +++ b/x-pack/plugins/upgrade_assistant/kibana.jsonc @@ -21,7 +21,8 @@ "usageCollection", "cloud", "security", - "infra" + "infra", + "logsShared" ], "requiredBundles": [ "esUiShared", diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index f77a5eabe1bda..6dfb65be85d7b 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -16,7 +16,7 @@ import { SavedObjectsServiceStart, } from '@kbn/core/server'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; -import { InfraPluginSetup } from '@kbn/infra-plugin/server'; +import { LogsSharedPluginSetup } from '@kbn/logs-shared-plugin/server'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; @@ -43,7 +43,7 @@ interface PluginsSetup { usageCollection: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetup; - infra: InfraPluginSetup; + logsShared: LogsSharedPluginSetup; security?: SecurityPluginSetup; } @@ -83,7 +83,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { setup( { http, getStartServices, savedObjects }: CoreSetup, - { usageCollection, features, licensing, infra, security }: PluginsSetup + { usageCollection, features, licensing, logsShared, security }: PluginsSetup ) { this.licensing = licensing; @@ -105,7 +105,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { // We need to initialize the deprecation logs plugin so that we can // navigate from this app to the observability app using a source_id. - infra?.logViews.defineInternalLogView(DEPRECATION_LOGS_SOURCE_ID, { + logsShared?.logViews.defineInternalLogView(DEPRECATION_LOGS_SOURCE_ID, { name: 'deprecationLogs', description: 'deprecation logs', logIndices: { diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json index c9d8201c1607a..59d562c03c87d 100644 --- a/x-pack/plugins/upgrade_assistant/tsconfig.json +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/features-plugin", "@kbn/licensing-plugin", "@kbn/es-ui-shared-plugin", - "@kbn/infra-plugin", "@kbn/cloud-plugin", "@kbn/test-jest-helpers", "@kbn/share-plugin", @@ -37,6 +36,7 @@ "@kbn/core-elasticsearch-client-server-mocks", "@kbn/utility-types", "@kbn/shared-ux-router", + "@kbn/logs-shared-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts index 1d69200a277d4..9bd615b99e53b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts @@ -133,12 +133,16 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(1); for (const doc of docs) { - const { name, message } = doc._source.params; + const { name, message, anomalyExplorerUrl } = doc._source.params; expect(name).to.be('Test AD job'); expect(message).to.be( 'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.' ); + // check only part of the URL as time bounds vary based on the anomaly + expect(anomalyExplorerUrl).to.contain( + '/s/space1/app/ml/explorer/?_g=(ml%3A(jobIds%3A!(rt-anomaly-mean-value))' + ); } }); @@ -166,6 +170,7 @@ export default function alertTests({ getService }: FtrProviderContext) { params: { name: '{{{alertName}}}', message: '{{{context.message}}}', + anomalyExplorerUrl: '{{{context.anomalyExplorerUrl}}}', }, }, ], diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/get_csp_rule_template.ts b/x-pack/test/api_integration/apis/cloud_security_posture/get_csp_rule_template.ts index 1d91efdc7fe89..99fa403c22635 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/get_csp_rule_template.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/get_csp_rule_template.ts @@ -55,8 +55,9 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(500); - expect(body.message).to.be( - 'Please provide either benchmarkId or packagePolicyId, but not both' + expect(body.message).to.eql( + 'Please provide either benchmarkId or packagePolicyId, but not both', + `expected message to be 'Please provide either benchmarkId or packagePolicyId, but not both' but got ${body.message} instead` ); }); @@ -80,8 +81,9 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(500); - expect(body.message).to.be( - 'Please provide either benchmarkId or packagePolicyId, but not both' + expect(body.message).to.eql( + 'Please provide either benchmarkId or packagePolicyId, but not both', + `expected message to be 'Please provide either benchmarkId or packagePolicyId, but not both' but got ${body.message} instead` ); }); @@ -95,8 +97,14 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(404); - expect(body.statusCode).to.be(404); - expect(body.error).to.be('Not Found'); + expect(body.statusCode).to.eql( + 404, + `expected status code to be 404 but got ${body.statusCode} instead` + ); + expect(body.error).to.eql( + 'Not Found', + `expected error message to be 'Not Found' but got ${body.error} instead` + ); }); it(`Should return 200 status code and filter rules by benchmarkId`, async () => { @@ -124,7 +132,10 @@ export default function ({ getService }: FtrProviderContext) { (rule: CspRuleTemplate) => rule.metadata.benchmark.id === 'cis_k8s' ); - expect(allRulesHaveCorrectBenchmarkId).to.be(true); + expect(allRulesHaveCorrectBenchmarkId).to.eql( + true, + `expected true but got ${allRulesHaveCorrectBenchmarkId} instead` + ); }); it(`Should return 200 status code, and only requested fields in the response`, async () => { @@ -157,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { ); }); - expect(fieldsMatched).to.be(true); + expect(fieldsMatched).to.eql(true, `expected true but got ${fieldsMatched} instead`); }); it(`Should return 200 status code, items sorted by metadata.section field`, async () => { @@ -188,7 +199,8 @@ export default function ({ getService }: FtrProviderContext) { const isSorted = sections.every( (section, index) => index === 0 || section >= sections[index - 1] ); - expect(isSorted).to.be(true); + + expect(isSorted).to.eql(true, `expected true but got ${isSorted} instead`); }); it(`Should return 200 status code and paginate rules with a limit of PerPage`, async () => { @@ -213,7 +225,10 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(200); - expect(body.items.length).to.be(perPage); + expect(body.items.length).to.eql( + perPage, + `expected length to be ${perPage} but got ${body.items.length} instead` + ); }); }); } diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts index b659153b1bf69..79aede12385db 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts @@ -7,6 +7,8 @@ import type { SuperTest, Test } from 'supertest'; import { Client } from '@elastic/elasticsearch'; +import expect from '@kbn/expect'; +import type { IndexDetails } from '@kbn/cloud-security-posture-plugin/common/types'; import { SecurityService } from '../../../../../test/common/services/security/security'; export const deleteIndex = (es: Client, indexToBeDeleted: string[]) => { @@ -141,3 +143,15 @@ export const deleteRole = async (security: SecurityService, roleName: string) => export const deleteUser = async (security: SecurityService, userName: string) => { await security.user.delete(userName); }; + +export const assertIndexStatus = ( + indicesDetails: IndexDetails[], + indexName: string, + expectedStatus: string +) => { + const actualValue = indicesDetails.find((idx) => idx.index === indexName)?.status; + expect(actualValue).to.eql( + expectedStatus, + `expected ${indexName} status to be ${expectedStatus} but got ${actualValue} instead` + ); +}; diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts index eae7154763bc0..2203a6374db70 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { FINDINGS_INDEX_DEFAULT_NS, LATEST_FINDINGS_INDEX_DEFAULT_NS, @@ -105,10 +106,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.kspm.status).to.be('index-timeout'); + expect(res.kspm.status).to.eql( + 'index-timeout', + `expected kspm status to be index-timeout but got ${res.kspm.status} instead` + ); }); it(`Should return index-timeout when installed cspm, has findings only on logs-cloud_security_posture.findings-default* and it has been more than 10 minutes since the installation`, async () => { @@ -131,10 +136,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('index-timeout'); + expect(res.cspm.status).to.eql( + 'index-timeout', + `expected cspm status to be index-timeout but got ${res.cspm.status} instead` + ); }); it(`Should return index-timeout when installed cnvm, has findings only on logs-cloud_security_posture.vulnerabilities-default* and it has been more than 4 hours minutes since the installation`, async () => { @@ -157,10 +166,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.vuln_mgmt.status).to.be('index-timeout'); + expect(res.vuln_mgmt.status).to.eql( + 'index-timeout', + `expected vuln_mgmt status to be index-timeout but got ${res.vuln_mgmt.status} instead` + ); }); }); }); diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts index c8cd9927c72d4..594babe643b05 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; import { FINDINGS_INDEX_DEFAULT_NS, @@ -71,10 +72,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.kspm.status).to.be('indexed'); + expect(res.kspm.status).to.eql( + 'indexed', + `expected kspm status to be indexed but got ${res.kspm.status} instead` + ); }); it(`Return cspm status indexed when logs-cloud_security_posture.findings_latest-default contains new cspm documents`, async () => { @@ -89,10 +94,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('indexed'); + expect(res.cspm.status).to.eql( + 'indexed', + `expected cspm status to be indexed but got ${res.cspm.status} instead` + ); }); it(`Return vuln status indexed when logs-cloud_security_posture.vulnerabilities_latest-default contains new documents`, async () => { @@ -107,10 +116,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.vuln_mgmt.status).to.be('indexed'); + expect(res.vuln_mgmt.status).to.eql( + 'indexed', + `expected vuln_mgmt status to be indexed but got ${res.vuln_mgmt.status} instead` + ); }); }); }); diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts index d7c11c446544a..ef38ab85efb04 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; import { FINDINGS_INDEX_DEFAULT_NS, @@ -70,10 +71,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.kspm.status).to.be('indexing'); + expect(res.kspm.status).to.eql( + 'indexing', + `expected kspm status to be indexing but got ${res.kspm.status} instead` + ); }); it(`Return cspm status indexing when logs-cloud_security_posture.findings_latest-default doesn't contain new cspm documents, but has newly connected agents `, async () => { @@ -88,10 +93,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('indexing'); + expect(res.cspm.status).to.eql( + 'indexing', + `expected cspm status to be indexing but got ${res.cspm.status} instead` + ); }); it(`Return vuln status indexing when logs-cloud_security_posture.vulnerabilities_latest-default doesn't contain vuln new documents, but has newly connected agents`, async () => { @@ -106,10 +115,14 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.vuln_mgmt.status).to.be('indexing'); + expect(res.vuln_mgmt.status).to.eql( + 'indexing', + `expected vuln_mgmt status to be indexing but got ${res.vuln_mgmt.status} instead` + ); }); }); }); diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_not_deployed_not_installed.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_not_deployed_not_installed.ts index 93b8c81ad44de..dcfbedae15741 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_not_deployed_not_installed.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_not_deployed_not_installed.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { createPackagePolicy } from '../helper'; @@ -50,14 +51,30 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.kspm.status).to.be('not-deployed'); - expect(res.cspm.status).to.be('not-installed'); - expect(res.vuln_mgmt.status).to.be('not-installed'); - expect(res.kspm.healthyAgents).to.be(0); - expect(res.kspm.installedPackagePolicies).to.be(1); + expect(res.kspm.status).to.eql( + 'not-deployed', + `expected kspm status to be not-deployed but got ${res.kspm.status} instead` + ); + expect(res.cspm.status).to.eql( + 'not-installed', + `expected cspm status to be not-installed but got ${res.cspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'not-installed', + `expected vuln_mgmt status to be not-installed but got ${res.vuln_mgmt.status} instead` + ); + expect(res.kspm.healthyAgents).to.eql( + 0, + `expected number of kspm healthy agents to be 0 but got ${res.kspm.healthyAgents} instead` + ); + expect(res.kspm.installedPackagePolicies).to.eql( + 1, + `expected number of kspm installed package policies to be 1 but got ${res.kspm.installedPackagePolicies} instead` + ); }); it(`Should return not-deployed when installed cspm, no findings on either indices and no healthy agents`, async () => { @@ -72,14 +89,30 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('not-deployed'); - expect(res.kspm.status).to.be('not-installed'); - expect(res.vuln_mgmt.status).to.be('not-installed'); - expect(res.cspm.healthyAgents).to.be(0); - expect(res.cspm.installedPackagePolicies).to.be(1); + expect(res.cspm.status).to.eql( + 'not-deployed', + `expected cspm status to be not-deployed but got ${res.cspm.status} instead` + ); + expect(res.kspm.status).to.eql( + 'not-installed', + `expected kspm status to be not-installed but got ${res.kspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'not-installed', + `expected vuln_mgmt status to be not-installed but got ${res.vuln_mgmt.status} instead` + ); + expect(res.cspm.healthyAgents).to.eql( + 0, + `expected number of cspm healthy agents to be 0 but got ${res.cspm.healthyAgents} instead` + ); + expect(res.cspm.installedPackagePolicies).to.eql( + 1, + `expected number of cspm installed package policies to be 1 but got ${res.cspm.installedPackagePolicies} instead` + ); }); it(`Should return not-deployed when installed cnvm, no findings on either indices and no healthy agents`, async () => { @@ -94,14 +127,30 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('not-installed'); - expect(res.kspm.status).to.be('not-installed'); - expect(res.vuln_mgmt.status).to.be('not-deployed'); - expect(res.vuln_mgmt.healthyAgents).to.be(0); - expect(res.vuln_mgmt.installedPackagePolicies).to.be(1); + expect(res.cspm.status).to.eql( + 'not-installed', + `expected cspm status to be not-installed but got ${res.cspm.status} instead` + ); + expect(res.kspm.status).to.eql( + 'not-installed', + `expected kspm status to be not-installed but got ${res.kspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'not-deployed', + `expected vuln_mgmt status to be not-deployed but got ${res.vuln_mgmt.status} instead` + ); + expect(res.vuln_mgmt.healthyAgents).to.eql( + 0, + `expected number of vuln_mgmt healthy agents to be 0 but got ${res.vuln_mgmt.healthyAgents} instead` + ); + expect(res.vuln_mgmt.installedPackagePolicies).to.eql( + 1, + `expected number of vuln_mgmt installed package policies to be 1 but got ${res.vuln_mgmt.installedPackagePolicies} instead` + ); }); }); }); diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts index 3127519b2bc4c..7d1445932fa6c 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts @@ -5,11 +5,13 @@ * 2.0. */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; import { BENCHMARK_SCORE_INDEX_DEFAULT_NS, LATEST_FINDINGS_INDEX_DEFAULT_NS, LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, + FINDINGS_INDEX_PATTERN, } from '@kbn/cloud-security-posture-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { @@ -19,6 +21,7 @@ import { deleteRole, deleteUser, deleteIndex, + assertIndexStatus, } from '../helper'; const UNPRIVILEGED_ROLE = 'unprivileged_test_role'; @@ -79,13 +82,23 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .auth(UNPRIVILEGED_USERNAME, 'changeme') .expect(200); - expect(res.kspm.status).to.be('unprivileged'); - expect(res.cspm.status).to.be('unprivileged'); - expect(res.vuln_mgmt.status).to.be('unprivileged'); + expect(res.kspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.kspm.status} instead` + ); + expect(res.cspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.cspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.vuln_mgmt.status} instead` + ); }); }); @@ -127,18 +140,32 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .auth(UNPRIVILEGED_USERNAME, 'changeme') .expect(200); - expect(res.kspm.status).to.be('unprivileged'); - expect(res.cspm.status).to.be('unprivileged'); - expect(res.vuln_mgmt.status).to.be('unprivileged'); + expect(res.kspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.kspm.status} instead` + ); + expect(res.cspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.cspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.vuln_mgmt.status} instead` + ); - expect(res.indicesDetails[0].status).to.be('empty'); - expect(res.indicesDetails[1].status).to.be('empty'); - expect(res.indicesDetails[2].status).to.be('unprivileged'); - expect(res.indicesDetails[3].status).to.be('unprivileged'); + assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'empty'); + assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); + assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'unprivileged'); + assertIndexStatus( + res.indicesDetails, + LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, + 'unprivileged' + ); }); it(`Return unprivileged when missing access to score index`, async () => { @@ -157,18 +184,32 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .auth(UNPRIVILEGED_USERNAME, 'changeme') .expect(200); - expect(res.kspm.status).to.be('unprivileged'); - expect(res.cspm.status).to.be('unprivileged'); - expect(res.vuln_mgmt.status).to.be('unprivileged'); + expect(res.kspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.kspm.status} instead` + ); + expect(res.cspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.cspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.vuln_mgmt.status} instead` + ); - expect(res.indicesDetails[0].status).to.be('unprivileged'); - expect(res.indicesDetails[1].status).to.be('empty'); - expect(res.indicesDetails[2].status).to.be('empty'); - expect(res.indicesDetails[3].status).to.be('unprivileged'); + assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'unprivileged'); + assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); + assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'empty'); + assertIndexStatus( + res.indicesDetails, + LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, + 'unprivileged' + ); }); it(`Return unprivileged when missing access to vulnerabilities_latest index`, async () => { @@ -190,18 +231,28 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .auth(UNPRIVILEGED_USERNAME, 'changeme') .expect(200); - expect(res.kspm.status).to.be('unprivileged'); - expect(res.cspm.status).to.be('unprivileged'); - expect(res.vuln_mgmt.status).to.be('not-installed'); + expect(res.kspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.kspm.status} instead` + ); + expect(res.cspm.status).to.eql( + 'unprivileged', + `expected unprivileged but got ${res.cspm.status} instead` + ); + expect(res.vuln_mgmt.status).to.eql( + 'not-installed', + `expected not-installed but got ${res.vuln_mgmt.status} instead` + ); - expect(res.indicesDetails[0].status).to.be('unprivileged'); - expect(res.indicesDetails[1].status).to.be('empty'); - expect(res.indicesDetails[2].status).to.be('unprivileged'); - expect(res.indicesDetails[3].status).to.be('empty'); + assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'unprivileged'); + assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); + assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'unprivileged'); + assertIndexStatus(res.indicesDetails, LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, 'empty'); }); }); }); diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_waiting_for_results.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_waiting_for_results.ts index 8153851124329..bc6a44100dab0 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_waiting_for_results.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_waiting_for_results.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-plugin/common/types'; import { setupFleetAndAgents } from '../../../../fleet_api_integration/apis/agents/services'; import { generateAgent } from '../../../../fleet_api_integration/helpers'; @@ -87,9 +88,13 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.kspm.status).to.be('waiting_for_results'); + expect(res.kspm.status).to.eql( + 'waiting_for_results', + `expected kspm status to be waiting_for_results but got ${res.kspm.status} instead` + ); }); it(`Should return waiting_for_result when installed cspm, has no findings and it has been less than 10 minutes since the installation`, async () => { @@ -112,9 +117,13 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.cspm.status).to.be('waiting_for_results'); + expect(res.cspm.status).to.eql( + 'waiting_for_results', + `expected cspm status to be waiting_for_results but got ${res.cspm.status} instead` + ); }); it(`Should return waiting_for_result when installed cnvm, has no findings and it has been less than 4 hours minutes since the installation`, async () => { @@ -137,9 +146,13 @@ export default function (providerContext: FtrProviderContext) { const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.vuln_mgmt.status).to.be('waiting_for_results'); + expect(res.vuln_mgmt.status).to.eql( + 'waiting_for_results', + `expected vuln_mgmt status to be waiting_for_results but got ${res.vuln_mgmt.status} instead` + ); }); }); }); diff --git a/x-pack/test/api_integration/apis/logs_ui/log_views.ts b/x-pack/test/api_integration/apis/logs_ui/log_views.ts index 646d9fd3bb5e3..07b2ffdbf6832 100644 --- a/x-pack/test/api_integration/apis/logs_ui/log_views.ts +++ b/x-pack/test/api_integration/apis/logs_ui/log_views.ts @@ -6,14 +6,14 @@ */ import expect from '@kbn/expect'; -import { defaultLogViewId, LogViewAttributes } from '@kbn/infra-plugin/common/log_views'; +import { defaultLogViewId, LogViewAttributes } from '@kbn/logs-shared-plugin/common/log_views'; import { defaultSourceConfiguration, infraSourceConfigurationSavedObjectName, mergeSourceConfiguration, } from '@kbn/infra-plugin/server/lib/sources'; import { extractSavedObjectReferences } from '@kbn/infra-plugin/server/lib/sources/saved_object_references'; -import { logViewSavedObjectName } from '@kbn/infra-plugin/server/saved_objects/log_view'; +import { logViewSavedObjectName } from '@kbn/logs-shared-plugin/server'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts index 6efb17e1571cc..fd87af88ebc8b 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts @@ -17,7 +17,7 @@ import { LOG_ENTRIES_HIGHLIGHTS_PATH, logEntriesHighlightsRequestRT, logEntriesHighlightsResponseRT, -} from '@kbn/infra-plugin/common/http_api'; +} from '@kbn/logs-shared-plugin/common'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts index 9c7c5548fcdbc..4ae51422ea688 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts @@ -19,7 +19,7 @@ import { LOG_ENTRIES_SUMMARY_PATH, logEntriesSummaryRequestRT, logEntriesSummaryResponseRT, -} from '@kbn/infra-plugin/common/http_api'; +} from '@kbn/logs-shared-plugin/common'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts index b052a06e8bf52..f5d9256a13b4d 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts @@ -4,13 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { v4 as uuidv4 } from 'uuid'; +import { pick } from 'lodash'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PrivateLocationTestService } from './services/private_location_test_service'; +function assertHas(actual: unknown, expected: object) { + expect(pick(actual, Object.keys(expected))).eql(expected); +} + export default function ({ getService }: FtrProviderContext) { describe('AddEditParams', function () { this.tags('skipCloud'); @@ -40,7 +46,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(getResponse.body.data[0].attributes).eql(testParam); + assertHas(getResponse.body[0], testParam); }); it('handles tags and description', async () => { @@ -63,11 +69,11 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(getResponse.body.data[0].attributes).eql(testParam2); + assertHas(getResponse.body[0], testParam2); }); it('handles editing a param', async () => { - const updatedParam = { + const expectedUpdatedParam = { key: 'testUpdated', value: 'testUpdated', tags: ['a tag'], @@ -84,21 +90,21 @@ export default function ({ getService }: FtrProviderContext) { .get(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') .expect(200); - const param = getResponse.body.data[0]; - expect(param.attributes).eql(testParam); + const param = getResponse.body[0]; + assertHas(param, testParam); await supertestAPI .put(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') - .send({ ...updatedParam, id: param.id }) + .send({ ...expectedUpdatedParam, id: param.id }) .expect(200); const updatedGetResponse = await supertestAPI .get(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') .expect(200); - const updatedParamSO = updatedGetResponse.body.data[0]; - expect(updatedParamSO.attributes).eql(updatedParam); + const actualUpdatedParam = updatedGetResponse.body[0]; + assertHas(actualUpdatedParam, expectedUpdatedParam); }); it('handles spaces', async () => { @@ -118,8 +124,8 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(getResponse.body.data[0].namespaces).eql([SPACE_ID]); - expect(getResponse.body.data[0].attributes).eql(testParam); + expect(getResponse.body[0].namespaces).eql([SPACE_ID]); + assertHas(getResponse.body[0], testParam); }); it('handles editing a param in spaces', async () => { @@ -128,7 +134,7 @@ export default function ({ getService }: FtrProviderContext) { await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - const updatedParam = { + const expectedUpdatedParam = { key: 'testUpdated', value: 'testUpdated', tags: ['a tag'], @@ -145,21 +151,21 @@ export default function ({ getService }: FtrProviderContext) { .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) .set('kbn-xsrf', 'true') .expect(200); - const param = getResponse.body.data[0]; - expect(param.attributes).eql(testParam); + const param = getResponse.body[0]; + assertHas(param, testParam); await supertestAPI .put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) .set('kbn-xsrf', 'true') - .send({ ...updatedParam, id: param.id }) + .send({ ...expectedUpdatedParam, id: param.id }) .expect(200); const updatedGetResponse = await supertestAPI .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) .set('kbn-xsrf', 'true') .expect(200); - const updatedParamSO = updatedGetResponse.body.data[0]; - expect(updatedParamSO.attributes).eql(updatedParam); + const actualUpdatedParam = updatedGetResponse.body[0]; + assertHas(actualUpdatedParam, expectedUpdatedParam); }); it('does not allow editing a param in created in one space in a different space', async () => { @@ -188,8 +194,8 @@ export default function ({ getService }: FtrProviderContext) { .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) .set('kbn-xsrf', 'true') .expect(200); - const param = getResponse.body.data[0]; - expect(param.attributes).eql(testParam); + const param = getResponse.body[0]; + assertHas(param, testParam); // space does exist so get request should be 200 await supertestAPI @@ -207,8 +213,8 @@ export default function ({ getService }: FtrProviderContext) { .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) .set('kbn-xsrf', 'true') .expect(200); - const updatedParamSO = updatedGetResponse.body.data[0]; - expect(updatedParamSO.attributes).eql(testParam); + const actualUpdatedParam = updatedGetResponse.body[0]; + assertHas(actualUpdatedParam, testParam); }); it('handles invalid spaces', async () => { @@ -241,8 +247,8 @@ export default function ({ getService }: FtrProviderContext) { .get(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') .expect(200); - const param = getResponse.body.data[0]; - expect(param.attributes).eql(testParam); + const param = getResponse.body[0]; + assertHas(param, testParam); await supertestAPI .put(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}`) @@ -268,8 +274,8 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(getResponse.body.data[0].namespaces).eql(['*']); - expect(getResponse.body.data[0].attributes).eql(testParam); + expect(getResponse.body[0].namespaces).eql(['*']); + assertHas(getResponse.body[0], testParam); }); }); } diff --git a/x-pack/test/api_integration/apis/synthetics/index.ts b/x-pack/test/api_integration/apis/synthetics/index.ts index 13c581bf168b0..b00ff699ca1b6 100644 --- a/x-pack/test/api_integration/apis/synthetics/index.ts +++ b/x-pack/test/api_integration/apis/synthetics/index.ts @@ -32,5 +32,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./add_edit_params')); loadTestFile(require.resolve('./add_monitor_project_private_location')); loadTestFile(require.resolve('./inspect_monitor')); + loadTestFile(require.resolve('./test_now_monitor')); }); } diff --git a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts index 7dea2de5453d7..ea465986fadc5 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts @@ -10,14 +10,17 @@ import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved import { SavedObject } from '@kbn/core-saved-objects-common/src/server_types'; import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { MonitorInspectResponse } from '@kbn/synthetics-plugin/public/apps/synthetics/state/monitor_management/api'; +import { v4 as uuidv4 } from 'uuid'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { KibanaSupertestProvider } from '../../../../../../test/api_integration/services/supertest'; export class SyntheticsMonitorTestService { private supertest: ReturnType; + private getService: FtrProviderContext['getService']; constructor(getService: FtrProviderContext['getService']) { this.supertest = getService('supertest'); + this.getService = getService; } async getMonitor(monitorId: string, decrypted: boolean = true, space?: string) { @@ -95,4 +98,34 @@ export class SyntheticsMonitorTestService { console.error(e); } } + + async addsNewSpace() { + const username = 'admin'; + const password = `${username}-password`; + const roleName = 'uptime-role'; + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + + const security = this.getService('security'); + const kibanaServer = this.getService('kibanaServer'); + + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + }); + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + + return { username, password, SPACE_ID }; + } } diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts index 2840f8f3935f6..e8182d97e0fee 100644 --- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts @@ -7,7 +7,7 @@ import { ConfigKey, HTTPFields, - SyntheticsParamSO, + SyntheticsParams, } from '@kbn/synthetics-plugin/common/runtime_types'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { omit } from 'lodash'; @@ -15,7 +15,6 @@ import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_mana import { PackagePolicy } from '@kbn/fleet-plugin/common'; import expect from '@kbn/expect'; import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects'; -import { SavedObject } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; @@ -157,8 +156,8 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.status).eql(200); - apiResponse.body.data.forEach(({ attributes }: SavedObject) => { - params[attributes.key] = attributes.value; + apiResponse.body.forEach(({ key, value }: SyntheticsParams) => { + params[key] = value; }); }); @@ -257,23 +256,25 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(getResponse.body.data.length).eql(2); + expect(getResponse.body.length).eql(2); - const paramsResponse = getResponse.body.data || []; + const paramsResponse = getResponse.body || []; const ids = paramsResponse.map((param: any) => param.id); - await supertestAPI + const deleteResponse = await supertestAPI .delete(SYNTHETICS_API_URLS.PARAMS) .query({ ids: JSON.stringify(ids) }) .set('kbn-xsrf', 'true') .expect(200); + expect(deleteResponse.body).to.have.length(2); + const getResponseAfterDelete = await supertestAPI .get(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') .expect(200); - expect(getResponseAfterDelete.body.data.length).eql(0); + expect(getResponseAfterDelete.body.length).eql(0); await supertestAPI .get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS) diff --git a/x-pack/test/api_integration/apis/synthetics/test_now_monitor.ts b/x-pack/test/api_integration/apis/synthetics/test_now_monitor.ts new file mode 100644 index 0000000000000..400e073f8a3f3 --- /dev/null +++ b/x-pack/test/api_integration/apis/synthetics/test_now_monitor.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; +import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import expect from '@kbn/expect'; +import { omit } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from './helper/get_fixture_json'; +import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; + +export default function ({ getService }: FtrProviderContext) { + describe('RunTestManually', function () { + this.tags('skipCloud'); + + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const monitorTestService = new SyntheticsMonitorTestService(getService); + const kibanaServer = getService('kibanaServer'); + + let newMonitor: MonitorFields; + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await supertest + .put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT) + .set('kbn-xsrf', 'true') + .expect(200); + newMonitor = getFixtureJson('http_monitor'); + }); + + it('runs test manually', async () => { + const resp = await monitorTestService.addMonitor(newMonitor); + + const res = await supertest + .get(SYNTHETICS_API_URLS.TRIGGER_MONITOR + `/${resp.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const result = res.body; + expect(typeof result.testRunId).to.eql('string'); + expect(typeof result.configId).to.eql('string'); + expect(result.schedule).to.eql({ number: '5', unit: 'm' }); + expect(result.locations).to.eql([ + { + id: 'eu-west-01', + label: 'Europe East', + geo: { lat: 33.2343132435, lon: 73.2342343434 }, + url: 'https://example-url.com', + isServiceManaged: true, + }, + { + id: 'eu-west-02', + label: 'Europe West', + geo: { lat: 33.2343132435, lon: 73.2342343434 }, + url: 'https://example-url.com', + isServiceManaged: true, + }, + ]); + + expect(omit(result.monitor, ['id', 'config_id'])).to.eql( + omit(newMonitor, ['id', 'config_id']) + ); + }); + + it('works in non default space', async () => { + const { username, SPACE_ID, password } = await monitorTestService.addsNewSpace(); + + const resp = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send(newMonitor) + .expect(200); + + const res = await supertest + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.TRIGGER_MONITOR}/${resp.body.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + const result = res.body; + expect(typeof result.testRunId).to.eql('string'); + expect(typeof result.configId).to.eql('string'); + expect(result.schedule).to.eql({ number: '5', unit: 'm' }); + expect(result.locations).to.eql([ + { + id: 'eu-west-01', + label: 'Europe East', + geo: { lat: 33.2343132435, lon: 73.2342343434 }, + url: 'https://example-url.com', + isServiceManaged: true, + }, + { + id: 'eu-west-02', + label: 'Europe West', + geo: { lat: 33.2343132435, lon: 73.2342343434 }, + url: 'https://example-url.com', + isServiceManaged: true, + }, + ]); + + expect(omit(result.monitor, ['id', 'config_id'])).to.eql( + omit(newMonitor, ['id', 'config_id']) + ); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts index b09a37416024f..e5c2d205f1405 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts +++ b/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts @@ -7,6 +7,8 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { sumBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { @@ -88,15 +90,83 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'processor.event: "metric" AND metricset.name: "transaction" AND transaction.duration.summary :* ', docCount: 21, }, - { kuery: 'processor.event: "metric" AND metricset.name: "span_breakdown"', docCount: 15 }, { kuery: 'processor.event: "metric" AND metricset.name: "service_summary"', docCount: 21, }, + { kuery: 'processor.event: "metric" AND metricset.name: "span_breakdown"', docCount: 15 }, { kuery: 'processor.event: "transaction"', docCount: 450 }, ]); }); + describe('transactions', async () => { + let body: APIReturnType<'GET /internal/apm/diagnostics'>; + + const expectedDocCount = 450; + + beforeEach(async () => { + const res = await apmApiClient.adminUser({ + endpoint: 'GET /internal/apm/diagnostics', + params: { + query: { start: new Date(start).toISOString(), end: new Date(end).toISOString() }, + }, + }); + + body = res.body; + }); + + it('raw transaction events', () => { + const rawTransactions = body.apmEvents.find(({ kuery }) => + kuery.includes('processor.event: "transaction"') + ); + + expect(rawTransactions?.docCount).to.be(expectedDocCount); + }); + + it('transaction metrics', () => { + const transactionMetrics = body.apmEvents.find(({ kuery }) => + kuery.includes('metricset.name: "transaction"') + ); + + const intervalDocCount = sumBy( + Object.values(transactionMetrics?.intervals ?? {}), + ({ metricDocCount }) => metricDocCount + ); + expect(transactionMetrics?.docCount).to.be(intervalDocCount); + expect(transactionMetrics?.docCount).to.be(21); + + expect(transactionMetrics?.intervals).to.eql({ + '1m': { metricDocCount: 15, eventDocCount: expectedDocCount }, + '10m': { metricDocCount: 4, eventDocCount: expectedDocCount }, + '60m': { metricDocCount: 2, eventDocCount: expectedDocCount }, + }); + }); + + it('service transactions', () => { + const serviceTransactionMetrics = body.apmEvents.find(({ kuery }) => + kuery.includes('metricset.name: "service_transaction"') + ); + + const intervalDocCount = sumBy( + Object.values(serviceTransactionMetrics?.intervals ?? {}), + ({ metricDocCount }) => metricDocCount + ); + + expect(serviceTransactionMetrics?.docCount).to.be(intervalDocCount); + expect(serviceTransactionMetrics?.docCount).to.be(21); + + expect(serviceTransactionMetrics?.kuery).to.be( + 'processor.event: "metric" AND metricset.name: "service_transaction" AND transaction.duration.summary :* ' + ); + + expect(serviceTransactionMetrics?.intervals).to.eql({ + '1m': { metricDocCount: 15, eventDocCount: expectedDocCount }, + '10m': { metricDocCount: 4, eventDocCount: expectedDocCount }, + '60m': { metricDocCount: 2, eventDocCount: expectedDocCount }, + }); + }); + }); + it('returns zero doc_counts when filtering by a non-existing service', async () => { const { body } = await apmApiClient.adminUser({ endpoint: 'GET /internal/apm/diagnostics', diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts index 2d1a7c2ab69a6..8907f7e28841c 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts @@ -90,27 +90,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(response.attachments[1].id).to.eql(updatedCase.comments![1].id); }); - it('returns an empty array when no ids are requested', async () => { - const { attachments, errors } = await bulkGetAttachments({ - attachmentIds: [], - caseId: updatedCase.id, - supertest, - expectedHttpCode: 200, - }); - - expect(attachments.length).to.be(0); - expect(errors.length).to.be(0); - }); - - it('returns a 400 when more than 10k ids are requested', async () => { - await bulkGetAttachments({ - attachmentIds: Array.from(Array(10001).keys()).map((item) => item.toString()), - caseId: updatedCase.id, - supertest, - expectedHttpCode: 400, - }); - }); - it('populates the errors field with attachments that could not be found', async () => { const response = await bulkGetAttachments({ attachmentIds: [updatedCase.comments![0].id, 'does-not-exist'], @@ -455,5 +434,25 @@ export default ({ getService }: FtrProviderContext): void => { }); } }); + + describe('errors', () => { + it('400s when requesting more than 100 attachments', async () => { + await bulkGetAttachments({ + attachmentIds: Array(101).fill('foobar'), + caseId: 'id', + expectedHttpCode: 400, + supertest, + }); + }); + + it('400s when requesting zero attachments', async () => { + await bulkGetAttachments({ + attachmentIds: [], + caseId: 'id', + expectedHttpCode: 400, + supertest, + }); + }); + }); }); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_cases.ts index 3ea30d7371b3e..449251377a077 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_cases.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { CommentType } from '@kbn/cases-plugin/common'; +import { MAX_BULK_GET_CASES } from '@kbn/cases-plugin/common/constants'; import { getPostCaseRequest, postCaseReq } from '../../../../common/lib/mock'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -111,12 +112,18 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('errors', () => { - it('400s when requesting more than 1000 cases', async () => { - const ids = Array(1001).fill('test'); + it(`400s when requesting more than ${MAX_BULK_GET_CASES} cases`, async () => { + await bulkGetCases({ + supertest, + ids: Array(MAX_BULK_GET_CASES + 1).fill('foobar'), + expectedHttpCode: 400, + }); + }); + it('400s when requesting zero cases', async () => { await bulkGetCases({ supertest, - ids, + ids: [], expectedHttpCode: 400, }); }); diff --git a/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts b/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts index ede036d93239f..797a88881a87b 100644 --- a/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts +++ b/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { data, MockTelemetryFindings } from './data'; import type { FtrProviderContext } from '../ftr_provider_context'; @@ -26,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { log.debug('Check CSP plugin is initialized'); const response = await supertest .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .expect(200); expect(response.body).to.eql({ isPluginInitialized: true }); log.debug('CSP plugin is initialized'); diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts index 635cf88965e5f..c7265ece4744a 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../ftr_provider_context'; // Defined in CSP plugin @@ -27,6 +28,7 @@ export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProv log.debug('Check CSP plugin is initialized'); const response = await supertest .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .expect(200); expect(response.body).to.eql({ isPluginInitialized: true }); log.debug('CSP plugin is initialized'); diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts index 6bb3a78e44cb4..59ec355ca770f 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../ftr_provider_context'; // Defined in CSP plugin @@ -28,6 +29,7 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider log.debug('Check CSP plugin is initialized'); const response = await supertest .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .expect(200); expect(response.body).to.eql({ isPluginInitialized: true }); log.debug('CSP plugin is initialized'); diff --git a/x-pack/test/common/services/infra_log_views.ts b/x-pack/test/common/services/infra_log_views.ts index a6a87da67e1aa..3862295e997ad 100644 --- a/x-pack/test/common/services/infra_log_views.ts +++ b/x-pack/test/common/services/infra_log_views.ts @@ -10,8 +10,8 @@ import { PutLogViewRequestPayload, putLogViewRequestPayloadRT, putLogViewResponsePayloadRT, -} from '@kbn/infra-plugin/common/http_api'; -import { getLogViewUrl } from '@kbn/infra-plugin/common/http_api/log_views'; +} from '@kbn/logs-shared-plugin/common/http_api'; +import { getLogViewUrl } from '@kbn/logs-shared-plugin/common/http_api/log_views'; import { decodeOrThrow } from '@kbn/infra-plugin/common/runtime_types'; import { FtrProviderContext } from '../ftr_provider_context'; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts new file mode 100644 index 0000000000000..28f71daa1f0f4 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrConfigProviderContext } from '@kbn/test'; +import path from 'path'; + +export const BUNDLED_PACKAGE_DIR = path.join( + path.dirname(__filename), + './fleet_bundled_packages/fixtures' +); + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [ + require.resolve('./prerelease_packages.ts'), + require.resolve('./install_latest_bundled_prebuilt_rules.ts'), + ], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. + * To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the + * location defined in BUNDLED_PACKAGE_DIR. + */ + `--xpack.fleet.registryUrl=http://invalidURL:8080`, + `--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`, + ], + }, + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip new file mode 100644 index 0000000000000..7c725ce134e42 Binary files /dev/null and b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip differ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip new file mode 100644 index 0000000000000..d9f1118bca032 Binary files /dev/null and b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip differ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts new file mode 100644 index 0000000000000..c92dc5532255e --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import fs from 'fs/promises'; +import path from 'path'; +// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail +import { REPO_ROOT } from '@kbn/repo-info'; +import JSON5 from 'json5'; +import expect from 'expect'; +import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + deleteAllPrebuiltRuleAssets, + deleteAllRules, + getPrebuiltRulesAndTimelinesStatus, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + /* This test simulates an air-gapped environment in which the user doesn't have access to EPR. + /* We first download the package from the registry as done during build time, and then + /* attempt to install it from the local file system. The API response from EPM provides + /* us with the information of whether the package was installed from the registry or + /* from a package that was bundled with Kibana */ + describe('install_bundled_prebuilt_rules', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es); + }); + + it('should list `security_detection_engine` as a bundled fleet package in the `fleet_package.json` file', async () => { + const configFilePath = path.resolve(REPO_ROOT, 'fleet_packages.json'); + const fleetPackages = await fs.readFile(configFilePath, 'utf8'); + + const parsedFleetPackages: PackageSpecManifest[] = JSON5.parse(fleetPackages); + + const securityDetectionEnginePackage = parsedFleetPackages.find( + (fleetPackage) => fleetPackage.name === 'security_detection_engine' + ); + + expect(securityDetectionEnginePackage).not.toBeUndefined(); + + expect(securityDetectionEnginePackage?.name).toBe('security_detection_engine'); + }); + + it('should install prebuilt rules from the package that comes bundled with Kibana', async () => { + // Verify that status is empty before package installation + const statusBeforePackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusBeforePackageInstallation.rules_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); + + const EPM_URL = `/api/fleet/epm/packages/security_detection_engine/99.0.0`; + + const bundledInstallResponse = await supertest + .post(EPM_URL) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }) + .expect(200); + + // As opposed to "registry" + expect(bundledInstallResponse.body._meta.install_source).toBe('bundled'); + + // Verify that status is updated after package installation + const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusAfterPackageInstallation.rules_installed).toBe(0); + expect(statusAfterPackageInstallation.rules_not_installed).toBeGreaterThan(0); + expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts new file mode 100644 index 0000000000000..0079e67c91f7e --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; +import expect from 'expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + deleteAllPrebuiltRuleAssets, + deleteAllRules, + getPrebuiltRulesAndTimelinesStatus, + installPrebuiltRulesAndTimelines, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + /* This test makes use of the mock packages created in the '/fleet_bundled_packages' folder, + /* in order to assert that, in production environments, the latest stable version of the package + /* is installed, and that prerelease packages are ignored. + /* The mock packages to test are 99.0.0 and 99.0.1-beta.1, where the latter is a prerelease package. + /* (We use high mock version numbers to prevent clashes with real packages downloaded in other tests.) + /* To do assertions on which packages have been installed, 99.0.0 has a single rule to install, + /* while 99.0.1-beta.1 has 2 rules to install. Also, both packages have the version as part of the rule names. */ + describe('prerelease_packages', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es); + }); + + it('should install latest stable version and ignore prerelease packages', async () => { + // Verify that status is empty before package installation + const statusBeforePackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusBeforePackageInstallation.rules_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); + + await installPrebuiltRulesAndTimelines(supertest); + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + // Verify that status is updated after package installation + const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusAfterPackageInstallation.rules_installed).toBe(1); // 1 rule in package 99.0.0 + expect(statusAfterPackageInstallation.rules_not_installed).toBe(0); + expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); + + // Get installed rules + const { body: rulesResponse } = await supertest + .get(DETECTION_ENGINE_RULES_URL_FIND) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Assert that installed rules are from package 99.0.0 and not from prerelease (beta) package + expect(rulesResponse.data.length).toBe(1); + expect(rulesResponse.data[0].name).not.toContain('beta'); + expect(rulesResponse.data[0].name).toContain('99.0.0'); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index 4bf702f4d2a75..f181b10e25bbc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -29,10 +29,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_rule_exception_references')); - loadTestFile(require.resolve('./get_prebuilt_rules_status')); - loadTestFile(require.resolve('./get_prebuilt_timelines_status')); - loadTestFile(require.resolve('./install_prebuilt_rules')); loadTestFile(require.resolve('./get_rule_management_filters')); - loadTestFile(require.resolve('./fleet_integration')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/open_close_signals.ts index c9fb6334ab8c5..72262b12453f8 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/open_close_signals.ts @@ -20,7 +20,7 @@ import { createSignalsIndex, deleteAllAlerts, setSignalStatus, - getAlertUpdateEmptyResponse, + getAlertUpdateByQueryEmptyResponse, getQuerySignalIds, deleteAllRules, createRule, @@ -41,33 +41,106 @@ export default ({ getService }: FtrProviderContext) => { describe('open_close_signals', () => { describe('validation checks', () => { - it('should not give errors when querying and the signals index does not exist yet', async () => { - const { body } = await supertest - .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) - .set('kbn-xsrf', 'true') - .send(setSignalStatus({ signalIds: ['123'], status: 'open' })) - .expect(200); + describe('update by ids', () => { + it('should not give errors when querying and the signals index does not exist yet', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds: ['123'], status: 'open' })) + .expect(200); - // remove any server generated items that are indeterministic - delete body.took; + // remove any server generated items that are nondeterministic + body.items.forEach((_: any, index: number) => { + delete body.items[index].update.error.index_uuid; + }); + delete body.took; + + expect(body).to.eql({ + errors: true, + items: [ + { + update: { + _id: '123', + _index: '.internal.alerts-security.alerts-default-000001', + error: { + index: '.internal.alerts-security.alerts-default-000001', + reason: '[123]: document missing', + shard: '0', + type: 'document_missing_exception', + }, + status: 404, + }, + }, + ], + }); + }); - expect(body).to.eql(getAlertUpdateEmptyResponse()); + it('should not give errors when querying and the signals index does exist and is empty', async () => { + await createSignalsIndex(supertest, log); + const { body } = await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds: ['123'], status: 'open' })) + .expect(200); + + // remove any server generated items that are nondeterministic + body.items.forEach((_: any, index: number) => { + delete body.items[index].update.error.index_uuid; + }); + delete body.took; + + expect(body).to.eql({ + errors: true, + items: [ + { + update: { + _id: '123', + _index: '.internal.alerts-security.alerts-default-000001', + error: { + index: '.internal.alerts-security.alerts-default-000001', + reason: '[123]: document missing', + shard: '0', + type: 'document_missing_exception', + }, + status: 404, + }, + }, + ], + }); + + await deleteAllAlerts(supertest, log, es); + }); }); - it('should not give errors when querying and the signals index does exist and is empty', async () => { - await createSignalsIndex(supertest, log); - const { body } = await supertest - .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) - .set('kbn-xsrf', 'true') - .send(setSignalStatus({ signalIds: ['123'], status: 'open' })) - .expect(200); + describe('update by query', () => { + it('should not give errors when querying and the signals index does not exist yet', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ query: { match_all: {} }, status: 'open' })) + .expect(200); + + // remove any server generated items that are indeterministic + delete body.took; + + expect(body).to.eql(getAlertUpdateByQueryEmptyResponse()); + }); + + it('should not give errors when querying and the signals index does exist and is empty', async () => { + await createSignalsIndex(supertest, log); + const { body } = await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ query: { match_all: {} }, status: 'open' })) + .expect(200); - // remove any server generated items that are indeterministic - delete body.took; + // remove any server generated items that are indeterministic + delete body.took; - expect(body).to.eql(getAlertUpdateEmptyResponse()); + expect(body).to.eql(getAlertUpdateByQueryEmptyResponse()); - await deleteAllAlerts(supertest, log, es); + await deleteAllAlerts(supertest, log, es); + }); }); describe('tests with auditbeat data', () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/set_alert_tags.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/set_alert_tags.ts index c3334cbe1c504..970d8b5426acd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/set_alert_tags.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/set_alert_tags.ts @@ -17,7 +17,6 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, deleteAllAlerts, - getAlertUpdateEmptyResponse, getQuerySignalIds, deleteAllRules, createRule, @@ -25,6 +24,7 @@ import { getSignalsByIds, waitForRuleSuccess, getRuleForSignalTesting, + getAlertUpdateByQueryEmptyResponse, } from '../../utils'; import { buildAlertTagsQuery, setAlertTags } from '../../utils/set_alert_tags'; @@ -47,7 +47,7 @@ export default ({ getService }: FtrProviderContext) => { // remove any server generated items that are indeterministic delete body.took; - expect(body).to.eql(getAlertUpdateEmptyResponse()); + expect(body).to.eql(getAlertUpdateByQueryEmptyResponse()); }); it('should give errors when duplicate tags exist in both tags_to_add and tags_to_remove', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts new file mode 100644 index 0000000000000..cba7488572593 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.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 { FtrConfigProviderContext } from '@kbn/test'; +import path from 'path'; + +export const BUNDLED_PACKAGE_DIR = path.join( + path.dirname(__filename), + './fleet_bundled_packages/fixtures' +); + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('./install_large_prebuilt_rules_package.ts')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. + * To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the + * location defined in BUNDLED_PACKAGE_DIR. + * Since we want to test the installation of a large package, we created a specific package `security_detection_engine-100.0.0` + * which contains 15000 rules assets and 750 unique rules, and attempt to install it. + */ + `--xpack.fleet.registryUrl=http://invalidURL:8080`, + `--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`, + ], + env: { + /* Limit the heap memory to the lowest amount with which Kibana doesn't crash with an out of memory error + * when installing the large package. + */ + NODE_OPTIONS: '--max-old-space-size=700', + }, + }, + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip new file mode 100644 index 0000000000000..27c81a0c89a31 Binary files /dev/null and b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip differ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts new file mode 100644 index 0000000000000..9dd5e695c3772 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from 'expect'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteAllRules, getPrebuiltRulesAndTimelinesStatus } from '../../utils'; +import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; +import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('install_large_prebuilt_rules_package', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es); + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es); + }); + + it('should install a package containing 15000 prebuilt rules without crashing', async () => { + // Verify that status is empty before package installation + const statusBeforePackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusBeforePackageInstallation.rules_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_installed).toBe(0); + expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); + + // Install the package with 15000 prebuilt historical version of rules rules and 750 unique rules + await installPrebuiltRulesAndTimelines(supertest); + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + // Verify that status is updated after package installation + const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusAfterPackageInstallation.rules_installed).toBe(750); + expect(statusAfterPackageInstallation.rules_not_installed).toBe(0); + expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts new file mode 100644 index 0000000000000..2430b8f2148d9 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.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 { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prebuilt_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts similarity index 78% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prebuilt_rules_status.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts index 2252c8317632a..6d728e0f8d548 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prebuilt_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, deleteAllRules, + deleteRule, getPrebuiltRulesAndTimelinesStatus, getSimpleRule, installPrebuiltRulesAndTimelines, @@ -90,6 +91,20 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + await deleteRule(supertest, 'rule-1'); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT - 1, + rules_not_installed: 1, + rules_not_updated: 0, + }); + }); + it('should return available rule updates', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); @@ -109,6 +124,25 @@ export default ({ getService }: FtrProviderContext): void => { rules_not_updated: 1, }); }); + + it('should not return any updates if none are available', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRulesAndTimelines(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Recreate the rules without bumping any versions + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 0, + }); + }); }); describe(`rule package with historical versions`, () => { @@ -146,7 +180,21 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should return available rule updates when previous historical versions available)', async () => { + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + await deleteRule(supertest, 'rule-1'); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT - 1, + rules_not_installed: 1, + rules_not_updated: 0, + }); + }); + + it('should return available rule updates when previous historical versions available', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRulesAndTimelines(supertest); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prebuilt_timelines_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prebuilt_timelines_status.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts new file mode 100644 index 0000000000000..7b376d5986040 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/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 { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('detection engine api security and spaces enabled - Prebuilt Rules', function () { + loadTestFile(require.resolve('./get_prebuilt_rules_status')); + loadTestFile(require.resolve('./get_prebuilt_timelines_status')); + loadTestFile(require.resolve('./install_prebuilt_rules')); + loadTestFile(require.resolve('./fleet_integration')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_prebuilt_rules.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/install_prebuilt_rules.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_prebuilt_rules.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/get_signal_status_empty_response.ts b/x-pack/test/detection_engine_api_integration/utils/get_signal_status_empty_response.ts index 3a7d612fea854..953d1ac4e77f8 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_signal_status_empty_response.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_signal_status_empty_response.ts @@ -5,7 +5,7 @@ * 2.0. */ -export const getAlertUpdateEmptyResponse = () => ({ +export const getAlertUpdateByQueryEmptyResponse = () => ({ timed_out: false, total: 0, updated: 0, diff --git a/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts b/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts index 2cd4cb557f637..37a10bd23f9a9 100644 --- a/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts +++ b/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts @@ -12,11 +12,14 @@ import type { export const setSignalStatus = ({ signalIds, + query, status, }: { - signalIds: SignalIds; + signalIds?: SignalIds; + query?: object; status: Status; }) => ({ signal_ids: signalIds, + query, status, }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts index 6f548d3d955d0..174d511674ef6 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts @@ -23,6 +23,25 @@ export default function (providerContext: FtrProviderContext) { const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; + const cleanupFiles = async () => { + await esClient.deleteByQuery({ + index: `${FILE_STORAGE_DATA_AGENT_INDEX},${FILE_STORAGE_METADATA_AGENT_INDEX}`, + refresh: true, + ignore_unavailable: true, + query: { + bool: { + filter: [ + { + ids: { + values: ['file1', 'file1.0'], + }, + }, + ], + }, + }, + }); + }; + describe('fleet_uploads', () => { skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); @@ -30,6 +49,7 @@ export default function (providerContext: FtrProviderContext) { before(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); await getService('supertest').post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); + await cleanupFiles(); await esClient.create({ index: AGENT_ACTIONS_INDEX, @@ -60,34 +80,36 @@ export default function (providerContext: FtrProviderContext) { ES_INDEX_OPTIONS ); - await esClient.update({ + await esClient.index({ index: FILE_STORAGE_METADATA_AGENT_INDEX, id: 'file1', refresh: true, + op_type: 'create', body: { - doc_as_upsert: true, - doc: { - upload_id: 'file1', - action_id: 'action1', - agent_id: 'agent1', - file: { - ChunkSize: 4194304, - extension: 'zip', - hash: {}, - mime_type: 'application/zip', - mode: '0644', - name: 'elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', - path: '/agent/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', - size: 24917, - Status: 'READY', - type: 'file', - }, + '@timestamp': new Date().toISOString(), + upload_id: 'file1', + action_id: 'action1', + agent_id: 'agent1', + file: { + ChunkSize: 4194304, + extension: 'zip', + hash: {}, + mime_type: 'application/zip', + mode: '0644', + name: 'elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + path: '/agent/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + size: 24917, + Status: 'READY', + type: 'file', }, }, }); }); after(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + await Promise.all([ + esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'), + cleanupFiles(), + ]); }); it('should get agent uploads', async () => { @@ -108,17 +130,16 @@ export default function (providerContext: FtrProviderContext) { }); it('should get agent uploaded file', async () => { - await esClient.update({ + await esClient.index({ index: FILE_STORAGE_DATA_AGENT_INDEX, id: 'file1.0', + op_type: 'create', refresh: true, body: { - doc_as_upsert: true, - doc: { - last: true, - bid: 'file1', - data: 'test', - }, + '@timestamp': new Date().toISOString(), + last: true, + bid: 'file1', + data: 'test', }, }); diff --git a/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts index 84913086f3c89..61b19327428e7 100644 --- a/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts +++ b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts @@ -16,6 +16,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); + const observability = getService('observability'); const testData = { correlationsTab: 'Failed transaction correlations', @@ -149,6 +150,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); }); }); + + it('navigates to the alerts tab', async function () { + await find.clickByCssSelector(`[data-test-subj="alertsTab"]`); + + await PageObjects.timePicker.timePickerExists(); + await PageObjects.timePicker.setCommonlyUsedTime('Last_15 minutes'); + + // Should show no data message + await retry.try(async () => { + await observability.overview.common.getAlertsTableNoDataOrFail(); + }); + }); }); }); } diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index bb914c45fa2b7..22049dc351caa 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -156,11 +156,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.click('TextBasedLangEditor-expand'); - await testSubjects.click('unifiedHistogramEditVisualization'); + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('lens visualization', async () => { + await retry.waitFor('lens flyout', async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average'; }); @@ -175,11 +175,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.click('TextBasedLangEditor-expand'); - await testSubjects.click('unifiedHistogramEditVisualization'); + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('lens visualization', async () => { + await retry.waitFor('lens flyout', async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average'; }); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 98481732be9d5..59ea0e5d21702 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -224,9 +224,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/157740 describe('Saved Views', () => { - this.tags('skipFirefox'); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.infraHome.goToMetricExplorer(); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts index cc10602114ef4..2c9c9cb7a6b98 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts @@ -6,32 +6,7 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; - -interface Detector { - identifier: string; - function: string; - field?: string; - byField?: string; - overField?: string; - partitionField?: string; - excludeFrequent?: string; - description?: string; -} - -interface DatafeedConfig { - queryDelay?: string; - frequency?: string; - scrollSize?: string; -} - -interface PickFieldsConfig { - detectors: Detector[]; - influencers: string[]; - bucketSpan: string; - memoryLimit: string; - categorizationField?: string; - summaryCountField?: string; -} +import type { PickFieldsConfig, DatafeedConfig, Detector } from './types'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_jobs_to_advanced_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_jobs_to_advanced_job.ts new file mode 100644 index 0000000000000..230f2d79f2448 --- /dev/null +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_jobs_to_advanced_job.ts @@ -0,0 +1,796 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '@kbn/ml-plugin/common/constants/categorization_job'; +import type { PickFieldsConfig, DatafeedConfig, Detector } from './types'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + const calendarId = `wizard-test-calendar_${Date.now()}`; + + const assertConversionToAdvancedJobWizardRetainsSettingsAndRuns = async ({ + testSuite, + testData, + previousJobGroups, + bucketSpan, + previousDetectors, + previousInfluencers, + }: { + testSuite: string; + testData: { + suiteTitle: string; + jobSource: string; + jobId: string; + jobDescription: string; + jobGroups: string[]; + pickFieldsConfig: PickFieldsConfig; + datafeedConfig: DatafeedConfig; + categorizationFieldIdentifier?: string; + }; + previousJobGroups: string[]; + bucketSpan: string; + previousDetectors: Array<{ advancedJobIdentifier: string }>; + previousInfluencers: string[]; + }) => { + const previousJobWizard = `${testSuite} job wizard`; + + it(`${testSuite} converts to advanced job and retains previous settings`, async () => { + await ml.testExecution.logTestStep(`${previousJobWizard} converts to advanced job creation`); + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.convertToAdvancedJobWizard(); + + await ml.testExecution.logTestStep('advanced job creation advances to the pick fields step'); + await ml.jobWizardCommon.advanceToPickFieldsSection(); + + await ml.testExecution.logTestStep('advanced job creation retains the categorization field'); + await ml.jobWizardAdvanced.assertCategorizationFieldSelection( + testData.categorizationFieldIdentifier ? [testData.categorizationFieldIdentifier] : [] + ); + + await ml.testExecution.logTestStep( + 'advanced job creation retains or inputs the summary count field' + ); + await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); + if (testData.pickFieldsConfig.hasOwnProperty('summaryCountField')) { + await ml.jobWizardAdvanced.selectSummaryCountField( + testData.pickFieldsConfig.summaryCountField! + ); + } else { + await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]); + } + + await ml.testExecution.logTestStep( + `advanced job creation retains detectors from ${previousJobWizard}` + ); + for (const [index, detector] of previousDetectors.entries()) { + await ml.jobWizardAdvanced.assertDetectorEntryExists(index, detector.advancedJobIdentifier); + } + + await ml.testExecution.logTestStep('advanced job creation adds additional detectors'); + for (const detector of testData.pickFieldsConfig.detectors) { + await ml.jobWizardAdvanced.openCreateDetectorModal(); + await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); + await ml.jobWizardAdvanced.assertDetectorFunctionSelection([]); + await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorFieldSelection([]); + await ml.jobWizardAdvanced.assertDetectorByFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorByFieldSelection([]); + await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorOverFieldSelection([]); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection([]); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection([]); + await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); + await ml.jobWizardAdvanced.assertDetectorDescriptionValue(''); + + await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); + if (detector.hasOwnProperty('field')) { + await ml.jobWizardAdvanced.selectDetectorField(detector.field!); + } + if (detector.hasOwnProperty('byField')) { + await ml.jobWizardAdvanced.selectDetectorByField(detector.byField!); + } + if (detector.hasOwnProperty('overField')) { + await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField!); + } + if (detector.hasOwnProperty('partitionField')) { + await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField!); + } + if (detector.hasOwnProperty('excludeFrequent')) { + await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent!); + } + if (detector.hasOwnProperty('description')) { + await ml.jobWizardAdvanced.setDetectorDescription(detector.description!); + } + + await ml.jobWizardAdvanced.confirmAddDetectorModal(); + } + + await ml.testExecution.logTestStep('advanced job creation displays detector entries'); + for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { + await ml.jobWizardAdvanced.assertDetectorEntryExists( + index + previousDetectors.length, + detector.identifier, + detector.hasOwnProperty('description') ? detector.description! : undefined + ); + } + + await ml.testExecution.logTestStep('advanced job creation retains the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + + await ml.testExecution.logTestStep( + `advanced job creation retains influencers from ${previousJobWizard}` + ); + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection(previousInfluencers); + for (const influencer of testData.pickFieldsConfig.influencers) { + await ml.jobWizardCommon.addInfluencer(influencer); + } + + await ml.testExecution.logTestStep('advanced job creation inputs the model memory limit'); + await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ + withAdvancedSection: false, + }); + await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, { + withAdvancedSection: false, + }); + + await ml.testExecution.logTestStep('advanced job creation displays the job details step'); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep( + `advanced job creation retains the job id from ${previousJobWizard}` + ); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(testData.jobId); + + await ml.testExecution.logTestStep( + `advanced job creation retains the job description from ${previousJobWizard}` + ); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(testData.jobDescription); + + await ml.testExecution.logTestStep( + `advanced job creation retains job groups and inputs new groups from ${previousJobWizard}` + ); + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of testData.jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection([ + ...previousJobGroups, + ...testData.jobGroups, + ]); + + await ml.testExecution.logTestStep( + 'advanced job creation opens the additional settings section' + ); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep( + `advanced job creation retains calendar and custom url from ${previousJobWizard}` + ); + await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); + await ml.jobWizardCommon.assertCustomUrlLabel(0, { label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('advanced job creation displays the validation step'); + await ml.jobWizardCommon.advanceToValidationSection(); + + await ml.testExecution.logTestStep('advanced job creation displays the summary step'); + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('advanced job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('advanced job creates the job and finishes processing'); + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardAdvanced.createJob(); + await ml.jobManagement.assertStartDatafeedModalExists(); + await ml.jobManagement.confirmStartDatafeedModal(); + + await ml.testExecution.logTestStep( + 'advanced job creation displays the created job in the job list' + ); + await ml.jobTable.filterWithSearchString(testData.jobId, 1); + }); + }; + + describe('conversion to advanced job wizard', function () { + this.tags(['ml']); + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/categorization_small'); + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); + await ml.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); + await ml.testResources.createIndexPatternIfNeeded('ft_categorization_small', '@timestamp'); + + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.api.createCalendar(calendarId); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_categorization_small'); + }); + + describe('from multi-metric job creation wizard', function () { + const jobGroups = ['automated', 'farequote', 'multi-metric']; + const splitField = 'customer_gender'; + const multiMetricDetectors = [ + { + identifier: 'High count(Event rate)', + advancedJobIdentifier: 'high_count partition_field_name=customer_gender', + }, + { + identifier: 'Low mean(products.base_price)', + advancedJobIdentifier: + 'low_mean("products.base_price") partition_field_name=customer_gender', + }, + ]; + const multiMetricInfluencers = ['customer_gender']; + + const bucketSpan = '2h'; + const memoryLimit = '8mb'; + + const testData = { + suiteTitle: 'multi-metric job to advanced job wizard', + jobSource: 'ft_ecommerce', + jobId: `ec_multimetric_to_advanced_1_${Date.now()}`, + jobDescription: 'advanced job from multi-metric wizard', + jobGroups: ['advanced'], + pickFieldsConfig: { + detectors: [], + influencers: ['geoip.region_name'], + bucketSpan: '1h', + memoryLimit: '10mb', + } as PickFieldsConfig, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + } as DatafeedConfig, + }; + + it('multi-metric job creation loads the multi-metric job wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.testExecution.logTestStep('job creation loads the job type selection page'); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_ecommerce'); + + await ml.testExecution.logTestStep('job creation loads the multi-metric job wizard page'); + await ml.jobTypeSelection.selectMultiMetricJob(); + }); + + it('multi-metric job creation navigates through the multi-metric job wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + + await ml.testExecution.logTestStep('job creation sets the time range'); + await ml.jobWizardCommon.clickUseFullDataButton( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the event rate chart' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the pick fields step' + ); + await ml.jobWizardCommon.advanceToPickFieldsSection(); + + for (const [ + index, + { identifier: aggAndFieldIdentifier }, + ] of multiMetricDetectors.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists( + aggAndFieldIdentifier, + index, + 'LINE' + ); + } + + await ml.testExecution.logTestStep( + 'multi-metric job creation inputs the split field and displays split cards' + ); + await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); + await ml.jobWizardMultiMetric.selectSplitField(splitField); + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('FEMALE'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(1); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the influencer field' + ); + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection(multiMetricInfluencers); + + await ml.testExecution.logTestStep('multi-metric job creation inputs the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the job details step' + ); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep('multi-metric job creation inputs the job id'); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(testData.jobId); + + await ml.testExecution.logTestStep('multi-metric job creation inputs the job description'); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(testData.jobDescription); + + await ml.testExecution.logTestStep('multi-metric job creation inputs job groups'); + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + + await ml.testExecution.logTestStep( + 'multi-metric job creation opens the additional settings section' + ); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep('multi-metric job creation adds a new custom url'); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('multi-metric job creation assigns calendars'); + await ml.jobWizardCommon.addCalendar(calendarId); + + await ml.testExecution.logTestStep('multi-metric job creation opens the advanced section'); + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the model plot switch' + ); + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + + await ml.testExecution.logTestStep( + 'multi-metric job creation enables the dedicated index switch' + ); + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + + await ml.testExecution.logTestStep( + 'multi-metric job creation inputs the model memory limit' + ); + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + + await ml.testExecution.logTestStep( + 'multi-metric job creation displays the validation step' + ); + await ml.jobWizardCommon.advanceToValidationSection(); + + await ml.testExecution.logTestStep('multi-metric job creation displays the summary step'); + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + assertConversionToAdvancedJobWizardRetainsSettingsAndRuns({ + testSuite: 'multi-metric', + testData, + bucketSpan, + previousInfluencers: multiMetricInfluencers, + previousDetectors: multiMetricDetectors, + previousJobGroups: jobGroups, + }); + }); + + describe('from population job creation wizard', function () { + const jobGroups = ['automated', 'ecommerce', 'population']; + const populationField = 'customer_id'; + const populationDetectors = [ + { + identifier: 'Mean(products.base_price)', + splitField: 'customer_gender', + frontCardTitle: 'FEMALE', + numberOfBackCards: 1, + advancedJobIdentifier: 'mean("products.base_price") by customer_gender over customer_id', + }, + { + identifier: 'Mean(products.quantity)', + splitField: 'category.keyword', + frontCardTitle: "Men's Clothing", + numberOfBackCards: 5, + advancedJobIdentifier: 'mean("products.quantity") by "category.keyword" over customer_id', + }, + ]; + const populationInfluencers = [populationField].concat( + populationDetectors.map((detector) => detector.splitField) + ); + + const bucketSpan = '2h'; + const memoryLimit = '8mb'; + + const testData = { + suiteTitle: 'population job to advanced job wizard', + jobSource: 'ft_ecommerce', + jobId: `ec_population_to_advanced_1_${Date.now()}`, + jobDescription: 'advanced job from population wizard', + jobGroups: ['advanced'], + pickFieldsConfig: { + detectors: [ + { + identifier: 'high_count', + function: 'high_count', + description: 'high_count detector without split', + } as Detector, + { + identifier: 'mean("products.base_price") by "category.keyword"', + function: 'mean', + field: 'products.base_price', + byField: 'category.keyword', + } as Detector, + { + identifier: 'sum("products.discount_amount") over customer_id', + function: 'sum', + field: 'products.discount_amount', + overField: 'customer_id', + } as Detector, + { + identifier: 'median(total_quantity) partition_field_name=customer_gender', + function: 'median', + field: 'total_quantity', + partitionField: 'customer_gender', + } as Detector, + { + identifier: + 'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender', + function: 'max', + field: 'total_quantity', + byField: 'geoip.continent_name', + overField: 'customer_id', + partitionField: 'customer_gender', + } as Detector, + ], + influencers: ['geoip.continent_name'], + bucketSpan: '1h', + memoryLimit: '10mb', + } as PickFieldsConfig, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + } as DatafeedConfig, + }; + + it('population job creation loads the population wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.testExecution.logTestStep('job creation loads the job type selection page'); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_ecommerce'); + + await ml.testExecution.logTestStep('job creation loads the population job wizard page'); + await ml.jobTypeSelection.selectPopulationJob(); + }); + + it('population job creation navigates through the population wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + + await ml.testExecution.logTestStep('job creation sets the time range'); + await ml.jobWizardCommon.clickUseFullDataButton( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + + await ml.testExecution.logTestStep('population job creation displays the event rate chart'); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + + await ml.testExecution.logTestStep('population job creation displays the pick fields step'); + await ml.jobWizardCommon.advanceToPickFieldsSection(); + + await ml.testExecution.logTestStep('population job creation selects the population field'); + await ml.jobWizardPopulation.selectPopulationField(populationField); + + await ml.testExecution.logTestStep( + 'population job creation selects populationDetectors and displays detector previews' + ); + for (const [index, detector] of populationDetectors.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists( + detector.identifier, + index, + 'SCATTER' + ); + } + + await ml.testExecution.logTestStep( + 'population job creation inputs detector split fields and displays split cards' + ); + for (const [index, detector] of populationDetectors.entries()) { + await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); + await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); + + await ml.jobWizardPopulation.assertDetectorSplitExists(index); + await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( + index, + detector.frontCardTitle + ); + await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( + index, + detector.numberOfBackCards + ); + } + + await ml.testExecution.logTestStep('population job creation displays the influencer field'); + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection(populationInfluencers); + + await ml.testExecution.logTestStep('population job creation inputs the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + + await ml.testExecution.logTestStep('population job creation displays the job details step'); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep('population job creation inputs the job id'); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(testData.jobId); + + await ml.testExecution.logTestStep('population job creation inputs the job description'); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(testData.jobDescription); + + await ml.testExecution.logTestStep('population job creation inputs job groups'); + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + + await ml.testExecution.logTestStep( + 'population job creation opens the additional settings section' + ); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep('population job creation adds a new custom url'); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('population job creation assigns calendars'); + await ml.jobWizardCommon.addCalendar(calendarId); + + await ml.testExecution.logTestStep('population job creation opens the advanced section'); + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + + await ml.testExecution.logTestStep( + 'population job creation displays the model plot switch' + ); + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + + await ml.testExecution.logTestStep( + 'population job creation enables the dedicated index switch' + ); + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + + await ml.testExecution.logTestStep('population job creation inputs the model memory limit'); + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + + await ml.testExecution.logTestStep('population job creation displays the validation step'); + await ml.jobWizardCommon.advanceToValidationSection(); + + await ml.testExecution.logTestStep('population job creation displays the summary step'); + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + assertConversionToAdvancedJobWizardRetainsSettingsAndRuns({ + testSuite: 'population', + testData, + bucketSpan, + previousInfluencers: populationInfluencers, + previousDetectors: populationDetectors, + previousJobGroups: jobGroups, + }); + }); + + describe('from categorization job creation wizard', function () { + const jobGroups = ['automated', 'categorization']; + const detectorTypeIdentifier = 'Rare'; + const categorizationFieldIdentifier = 'field1'; + const categorizationExampleCount = 5; + const bucketSpan = '1d'; + const memoryLimit = '15MB'; + const categorizationInfluencers = ['mlcategory']; + const testData = { + suiteTitle: 'categorization job to advanced job wizard', + jobSource: 'ft_ecommerce', + jobId: `categorization_to_advanced_${Date.now()}`, + jobDescription: 'advanced job from categorization wizard', + jobGroups: ['advanced'], + categorizationFieldIdentifier, + pickFieldsConfig: { + categorizationField: 'field1', + detectors: [], + influencers: [], + bucketSpan: '1h', + memoryLimit: '10mb', + } as PickFieldsConfig, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + } as DatafeedConfig, + }; + const categorizationDetectors = [{ advancedJobIdentifier: 'rare by mlcategory' }]; + + it('categorization job creation loads the categorization wizard for the source data', async () => { + await ml.testExecution.logTestStep( + 'categorization job creation loads the job management page' + ); + await ml.testExecution.logTestStep(''); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep( + 'categorization job creation loads the new job source selection page' + ); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.testExecution.logTestStep( + 'categorization job creation loads the job type selection page' + ); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_categorization_small'); + + await ml.testExecution.logTestStep( + 'categorization job creation loads the categorization job wizard page' + ); + await ml.jobTypeSelection.selectCategorizationJob(); + }); + + it('categorization job creation navigates through the categorization wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep( + 'categorization job creation displays the time range step' + ); + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + + await ml.testExecution.logTestStep('categorization job creation sets the time range'); + await ml.jobWizardCommon.clickUseFullDataButton( + 'Apr 5, 2019 @ 11:25:35.770', + 'Nov 21, 2019 @ 00:01:13.923' + ); + + await ml.testExecution.logTestStep( + 'categorization job creation displays the event rate chart' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + + await ml.testExecution.logTestStep( + 'categorization job creation displays the pick fields step' + ); + await ml.jobWizardCommon.advanceToPickFieldsSection(); + + await ml.testExecution.logTestStep( + `categorization job creation selects ${detectorTypeIdentifier} detector type` + ); + await ml.jobWizardCategorization.assertCategorizationDetectorTypeSelectionExists(); + await ml.jobWizardCategorization.selectCategorizationDetectorType(detectorTypeIdentifier); + + await ml.testExecution.logTestStep( + `categorization job creation selects the categorization field` + ); + await ml.jobWizardCategorization.selectCategorizationField( + testData.categorizationFieldIdentifier + ); + await ml.jobWizardCategorization.assertCategorizationExamplesCallout( + CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID + ); + await ml.jobWizardCategorization.assertCategorizationExamplesTable( + categorizationExampleCount + ); + + await ml.testExecution.logTestStep('categorization job creation inputs the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + + await ml.testExecution.logTestStep( + 'categorization job creation displays the job details step' + ); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep('categorization job creation inputs the job id'); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(testData.jobId); + + await ml.testExecution.logTestStep( + 'categorization job creation inputs the job description' + ); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(testData.jobDescription); + + await ml.testExecution.logTestStep('categorization job creation inputs job groups'); + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + + await ml.testExecution.logTestStep( + 'categorization job creation opens the additional settings section' + ); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep('categorization job creation adds a new custom url'); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('categorization job creation assigns calendars'); + await ml.jobWizardCommon.addCalendar(calendarId); + + await ml.testExecution.logTestStep( + 'categorization job creation opens the advanced section' + ); + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + + await ml.testExecution.logTestStep( + 'categorization job creation displays the model plot switch' + ); + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchEnabled(false); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); + + await ml.testExecution.logTestStep( + 'categorization job creation enables the dedicated index switch' + ); + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + + await ml.testExecution.logTestStep( + 'categorization job creation inputs the model memory limit' + ); + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + + await ml.testExecution.logTestStep( + 'categorization job creation displays the validation step' + ); + await ml.jobWizardCommon.advanceToValidationSection(); + + await ml.testExecution.logTestStep('categorization job creation displays the summary step'); + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + assertConversionToAdvancedJobWizardRetainsSettingsAndRuns({ + testSuite: 'categorization', + testData, + bucketSpan, + previousInfluencers: categorizationInfluencers, + previousDetectors: categorizationDetectors, + previousJobGroups: jobGroups, + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_single_metric_job_to_multi_metric.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_single_metric_job_to_multi_metric.ts new file mode 100644 index 0000000000000..c469440030038 --- /dev/null +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/convert_single_metric_job_to_multi_metric.ts @@ -0,0 +1,213 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + const esNode = config.get('esTestCluster.ccs') + ? getService('remoteEsArchiver' as 'esArchiver') + : getService('esArchiver'); + const ml = getService('ml'); + + const calendarId = `wizard-test-calendar_${Date.now()}`; + const remoteName = 'ftr-remote:'; + const indexPatternName = 'ft_farequote'; + const indexPatternString = config.get('esTestCluster.ccs') + ? remoteName + indexPatternName + : indexPatternName; + + describe('single metric job conversion to multi-metric job', function () { + this.tags(['ml']); + before(async () => { + await esNode.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded(indexPatternString, '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.api.createCalendar(calendarId); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.deleteIndexPatternByTitle(indexPatternString); + }); + + const jobId = `fq_single_to_multi_${Date.now()}`; + const jobDescription = 'Create multi metric job from single metric job'; + const jobGroups = ['automated', 'farequote', 'multi-metric']; + const smAggAndFieldIdentifier = 'Mean(responsetime)'; + const bucketSpan = '30m'; + + const mmAggAndFieldIdentifiers = [ + 'Min(responsetime)', + 'Max(responsetime)', + 'High mean(responsetime)', + ]; + const splitField = 'airline'; + + it('loads the single metric wizard for the source data', async () => { + await ml.testExecution.logTestStep('loads the job management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('loads the new job source selection page'); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.testExecution.logTestStep('loads the job type selection page'); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(indexPatternString); + + await ml.testExecution.logTestStep('loads the single metric job wizard page'); + await ml.jobTypeSelection.selectSingleMetricJob(); + }); + + it('navigates through the single metric wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('displays the time range step'); + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + + await ml.testExecution.logTestStep('sets the time range'); + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + + await ml.testExecution.logTestStep('displays the event rate chart'); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + + await ml.testExecution.logTestStep('displays the pick fields step'); + await ml.jobWizardCommon.advanceToPickFieldsSection(); + + await ml.testExecution.logTestStep('selects field and aggregation'); + await ml.jobWizardCommon.selectAggAndField(smAggAndFieldIdentifier, true); + await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); + + await ml.testExecution.logTestStep('inputs the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + + await ml.testExecution.logTestStep('displays the job details step'); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep('inputs the job id'); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + + await ml.testExecution.logTestStep('inputs the job description'); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(jobDescription); + + await ml.testExecution.logTestStep('inputs job groups'); + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + + await ml.testExecution.logTestStep('opens the additional settings section'); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep('adds a new custom url'); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('assigns calendars'); + await ml.jobWizardCommon.addCalendar(calendarId); + }); + + it('converts to multi-metric job creation wizard and retains all previously set fields', async () => { + await ml.testExecution.logTestStep( + 'navigates to previous page and converts to multi-metric job wizard' + ); + await ml.jobWizardCommon.navigateToPreviousJobWizardPage( + 'mlJobWizardButtonConvertToMultiMetric' + ); + await ml.jobWizardCommon.convertToMultiMetricJobWizard(); + await ml.jobWizardCommon.assertPickFieldsSectionExists(); + + await ml.testExecution.logTestStep( + 'multi-metric job wizard selects detectors and displays detector previews' + ); + for (const [index, aggAndFieldIdentifier] of mmAggAndFieldIdentifiers.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists( + aggAndFieldIdentifier, + // +1 to account for the one detector set from single metric job wizard + index + 1, + 'LINE' + ); + } + + await ml.jobWizardMultiMetric.selectSplitField(splitField); + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); + + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + + await ml.testExecution.logTestStep('multi-metric job wizard retains the bucket span'); + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + + await ml.testExecution.logTestStep('multi-metric job wizard displays the job details step'); + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.testExecution.logTestStep('multi-metric job wizard retains job id'); + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(jobId); + + await ml.testExecution.logTestStep('multi-metric job wizard retains the job description'); + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + + await ml.testExecution.logTestStep('multi-metric job wizard retains job groups'); + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + + await ml.testExecution.logTestStep( + 'multi-metric job wizard opens the additional settings section' + ); + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + + await ml.testExecution.logTestStep('multi-metric job wizard retains calendar and custom url'); + await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); + await ml.jobWizardCommon.assertCustomUrlLabel(0, { label: 'check-kibana-dashboard' }); + + await ml.testExecution.logTestStep('multi-metric job wizard displays the validation step'); + await ml.jobWizardCommon.advanceToValidationSection(); + + await ml.testExecution.logTestStep('multi-metric job wizard displays the summary step'); + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('runs the converted job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'multi-metric job wizard creates the job and finishes processing' + ); + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + + await ml.testExecution.logTestStep( + 'multi-metric job wizard displays the created job in the job list' + ); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.filterWithSearchString(jobId, 1); + + await ml.testExecution.logTestStep( + 'job list displays details for the created job in the job list' + ); + + await ml.testExecution.logTestStep('job has detector results'); + for (let i = 0; i < mmAggAndFieldIdentifiers.length; i++) { + await ml.api.assertDetectorResultsExist(jobId, i); + } + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts index ed9f63be66dd4..d63f6af6c0486 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts @@ -6,31 +6,7 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; - -interface Detector { - identifier: string; - function: string; - field?: string; - byField?: string; - overField?: string; - partitionField?: string; - excludeFrequent?: string; - description?: string; -} - -interface DatafeedConfig { - queryDelay?: string; - frequency?: string; - scrollSize?: string; -} - -interface PickFieldsConfig { - detectors: Detector[]; - influencers: string[]; - bucketSpan: string; - memoryLimit: string; - summaryCountField?: string; -} +import type { PickFieldsConfig, DatafeedConfig, Detector } from './types'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts index e350ced98aa79..254bd76f5616b 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts @@ -50,6 +50,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./date_nanos_job')); loadTestFile(require.resolve('./custom_urls')); loadTestFile(require.resolve('./delete_job_and_delete_annotations')); + loadTestFile(require.resolve('./convert_single_metric_job_to_multi_metric')); + loadTestFile(require.resolve('./convert_jobs_to_advanced_job')); } }); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/types.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/types.ts new file mode 100644 index 0000000000000..3aaa182ff7585 --- /dev/null +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface Detector { + identifier: string; + function: string; + field?: string; + byField?: string; + overField?: string; + partitionField?: string; + excludeFrequent?: string; + description?: string; +} + +export interface DatafeedConfig { + queryDelay?: string; + frequency?: string; + scrollSize?: string; +} + +export interface PickFieldsConfig { + detectors: Detector[]; + influencers: string[]; + bucketSpan: string; + memoryLimit: string; + categorizationField?: string; + summaryCountField?: string; +} diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 56c6e0d1354fb..218a7058f1141 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -54,7 +54,10 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { async createNewSavedView(name: string) { await testSubjects.setValue('savedViewName', name); await testSubjects.click('createSavedViewButton'); - await testSubjects.missingOrFail('savedViews-upsertModal'); + + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.missingOrFail('savedViews-upsertModal'); + }); }, async createView(name: string) { diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts index 5f3c685467c08..9d9c91f500456 100644 --- a/x-pack/test/functional/services/ml/job_wizard_common.ts +++ b/x-pack/test/functional/services/ml/job_wizard_common.ts @@ -17,7 +17,7 @@ export interface SectionOptions { } export function MachineLearningJobWizardCommonProvider( - { getService }: FtrProviderContext, + { getPageObject, getService }: FtrProviderContext, mlCommonUI: MlCommonUI, customUrls: MlCustomUrls, mlCommonFieldStatsFlyout: MlCommonFieldStatsFlyout @@ -25,6 +25,7 @@ export function MachineLearningJobWizardCommonProvider( const comboBox = getService('comboBox'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const headerPage = getPageObject('header'); function advancedSectionSelector(subSelector?: string) { const subj = 'mlJobWizardAdvancedSection'; @@ -492,6 +493,20 @@ export function MachineLearningJobWizardCommonProvider( await testSubjects.existOrFail('mlJobWizardButtonCreateJob'); }, + async assertConvertToAdvancedJobExists() { + await testSubjects.existOrFail('mlJobWizardButtonConvertToAdvancedJob'); + }, + + async convertToAdvancedJobWizard() { + await this.assertConvertToAdvancedJobExists(); + + await retry.tryForTime(5000, async () => { + await testSubjects.click('mlJobWizardButtonConvertToAdvancedJob'); + await headerPage.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('mlPageJobWizardHeader-advanced'); + }); + }, + async assertDateRangeSelectionExists() { await testSubjects.existOrFail('mlJobWizardDateRange'); }, @@ -558,6 +573,12 @@ export function MachineLearningJobWizardCommonProvider( await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label); }, + async assertCustomUrlLabel(expectedIndex: number, customUrl: { label: string }) { + await this.ensureAdditionalSettingsSectionOpen(); + + await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label); + }, + async ensureAdvancedSectionOpen() { await retry.tryForTime(5000, async () => { if ((await testSubjects.exists(advancedSectionSelector())) === false) { @@ -576,5 +597,27 @@ export function MachineLearningJobWizardCommonProvider( await testSubjects.clickWhenNotDisabledWithoutRetry('mlJobWizardButtonCreateJob'); await testSubjects.existOrFail('mlPageJobManagement'); }, + + async assertConvertToMultiMetricButtonExist(bucketSpan: string) { + await testSubjects.existOrFail('mlJobWizardButtonConvertToMultiMetric'); + }, + + async convertToMultiMetricJobWizard() { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.click('mlJobWizardButtonConvertToMultiMetric'); + await headerPage.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('mlPageJobWizardHeader-multi_metric'); + }); + }, + + async navigateToPreviousJobWizardPage(expectedSelector: string) { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.click('mlJobWizardNavButtonPrevious'); + await headerPage.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail(expectedSelector); + }); + }, }; } diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index d2c8dabaa2c33..c50a5403a166c 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -55,6 +55,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--home.disableWelcomeScreen=true`, // Specify which version of the detection-rules package to install // `--xpack.securitySolution.prebuiltRulesPackageVersion=8.3.1`, + // Set an inexistent directory as the Fleet bundled packages location + // in order to force Fleet to reach out to the registry to download the + // packages listed in fleet_packages.json + // See: https://elastic.slack.com/archives/CNMNXV4RG/p1683033379063079 + `--xpack.fleet.developer.bundledPackageLocation=./inexistentDir`, ], }, }; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.ts deleted file mode 100644 index ce8179a050c47..0000000000000 --- a/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.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 expect from '@kbn/expect'; -import { - FILE_STORAGE_DATA_INDEX, - FILE_STORAGE_METADATA_INDEX, -} from '@kbn/security-solution-plugin/common/endpoint/constants'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - - describe('File upload indices', () => { - it('should have created the file data index on install', async () => { - const endpointFileUploadIndexExists = await esClient.indices.exists({ - index: FILE_STORAGE_METADATA_INDEX, - }); - - expect(endpointFileUploadIndexExists).equal(true); - }); - it('should have created the files index on install', async () => { - const endpointFileUploadIndexExists = await esClient.indices.exists({ - index: FILE_STORAGE_DATA_INDEX, - }); - - expect(endpointFileUploadIndexExists).equal(true); - }); - }); -} diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index fbd33d38d1a94..e68db8182da20 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -48,7 +48,6 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider loadTestFile(require.resolve('./package')); loadTestFile(require.resolve('./endpoint_authz')); loadTestFile(require.resolve('./endpoint_response_actions/execute')); - loadTestFile(require.resolve('./file_upload_index')); loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps')); loadTestFile(require.resolve('./endpoint_artifacts/event_filters')); loadTestFile(require.resolve('./endpoint_artifacts/host_isolation_exceptions')); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 03496116b2072..4a2aff8ae7739 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -130,6 +130,7 @@ "@kbn/slo-schema", "@kbn/lens-plugin", "@kbn/notifications-plugin", + "@kbn/logs-shared-plugin", "@kbn/telemetry-tools", "@kbn/profiling-plugin", "@kbn/observability-onboarding-plugin" diff --git a/x-pack/test_serverless/api_integration/test_suites/common/spaces.ts b/x-pack/test_serverless/api_integration/test_suites/common/spaces.ts index 0998d4cdb317d..3184423411e47 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/spaces.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/spaces.ts @@ -15,7 +15,7 @@ export default function ({ getService }: FtrProviderContext) { describe('spaces', function () { it('rejects request to create a space', async () => { const { body, status } = await supertest - .post(`/api/spaces/space`) + .post('/api/spaces/space') .set(svlCommonApi.getCommonRequestHeader()) .send({ id: 'custom', @@ -32,5 +32,25 @@ export default function ({ getService }: FtrProviderContext) { }); expect(status).toBe(400); }); + + it('rejects request to update a space with disabledFeatures', async () => { + const { body, status } = await supertest + .put('/api/spaces/space/default') + .set(svlCommonApi.getCommonRequestHeader()) + .send({ + id: 'custom', + name: 'Custom', + disabledFeatures: ['some-feature'], + }); + + // in a non-serverless environment this would succeed with a 200 + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: + 'Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled', + }); + expect(status).toBe(400); + }); }); } diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index 81a3f197a0ec0..23739a9615e69 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -52,6 +52,9 @@ export function createTestConfig(options: CreateTestConfigOptions) { observability: { pathname: '/app/observability', }, + management: { + pathname: '/app/management', + }, }, // choose where screenshots should be saved screenshots: { diff --git a/x-pack/test_serverless/functional/test_suites/security/index.ts b/x-pack/test_serverless/functional/test_suites/security/index.ts index 470f1e98082e8..4d31c06188bcf 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.ts @@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless security UI', function () { loadTestFile(require.resolve('./landing_page')); + loadTestFile(require.resolve('./management')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/management.ts b/x-pack/test_serverless/functional/test_suites/security/management.ts new file mode 100644 index 0000000000000..f610e7b25ca73 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/management.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObject }: FtrProviderContext) { + const PageObject = getPageObject('common'); + + describe('Management', function () { + it('redirects from common management url to security specific page', async () => { + const SUB_URL = ''; + await PageObject.navigateToUrl('management', SUB_URL, { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + + await PageObject.waitUntilUrlIncludes('/security/manage'); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index dec901f6e1b29..3037b73b85d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4618,6 +4618,10 @@ version "0.0.0" uid "" +"@kbn/logs-shared-plugin@link:x-pack/plugins/logs_shared": + version "0.0.0" + uid "" + "@kbn/logstash-plugin@link:x-pack/plugins/logstash": version "0.0.0" uid ""