diff --git a/.buildkite/pipelines/flaky_tests/pipeline.ts b/.buildkite/pipelines/flaky_tests/pipeline.ts index 23a6803effe2f..24af68de09e22 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.ts +++ b/.buildkite/pipelines/flaky_tests/pipeline.ts @@ -177,6 +177,9 @@ for (const testSuite of testSuites) { // by setting chunks vars to value 1, which means all test will run in one job CLI_NUMBER: 1, CLI_COUNT: 1, + // The security solution cypress tests don't recognize CLI_NUMBER and CLI_COUNT, they use `BUILDKITE_PARALLEL_JOB_COUNT` and `BUILDKITE_PARALLEL_JOB`, which cannot be overridden here. + // Use `RUN_ALL_TESTS` to make Security Solution Cypress tests run all tests instead of a subset. + RUN_ALL_TESTS: 'true', }, }); break; diff --git a/.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh b/.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh index 8c715da15726c..65fd59f53b94c 100755 --- a/.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh +++ b/.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh @@ -3,4 +3,4 @@ set -euo pipefail echo "--- Trigger unsupported ftr tests" -ts-node .buildkite/scripts/steps/trigger_pipeline.ts kibana-on-merge-unsupported-ftrs "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT" +ts-node .buildkite/scripts/steps/trigger_pipeline.ts kibana-on-merge-unsupported-ftrs "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT" "$BUILDKITE_BUILD_ID" diff --git a/.buildkite/scripts/steps/trigger_pipeline.ts b/.buildkite/scripts/steps/trigger_pipeline.ts index f42a5b0843535..42a4ac561aac1 100644 --- a/.buildkite/scripts/steps/trigger_pipeline.ts +++ b/.buildkite/scripts/steps/trigger_pipeline.ts @@ -11,6 +11,7 @@ import { BuildkiteClient } from '#pipeline-utils'; const pipelineSlug = process.argv[2]; const branch = process.argv[3] || 'main'; const commit = process.argv[4] || 'HEAD'; +const kibanaBuildId = process.argv[5] || ''; (async () => { try { @@ -18,6 +19,9 @@ const commit = process.argv[4] || 'HEAD'; const build = await client.triggerBuild(pipelineSlug, { commit, branch, + env: { + ...(kibanaBuildId && { KIBANA_BUILD_ID: kibanaBuildId }), + }, ignore_pipeline_branch_filters: true, // Required because of a Buildkite bug }); console.log(`Triggered build: ${build.web_url}`); diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index fb8a5f0102418..3e1d1a486bd47 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-06-09 +date: 2023-06-12 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 8e25b6ef1e145..a4a7c8d8b67a5 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-06-09 +date: 2023-06-12 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 c6934b4fd4b15..fa93d41d18da3 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-06-09 +date: 2023-06-12 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 e30ff5f0a3b43..bdbab72297feb 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -2660,6 +2660,10 @@ "plugin": "observability", "path": "x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts" diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index b3495f62595d4..19c8cf5ccb6e7 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-06-09 +date: 2023-06-12 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 992eb561128c2..8f00c6095f6bf 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-06-09 +date: 2023-06-12 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 2f111284cfe3d..b3ad5376a4e28 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-06-09 +date: 2023-06-12 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 6c84ec445ee79..465bad2f89802 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-06-09 +date: 2023-06-12 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 0d6be2668ebf3..782c57f534730 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-06-09 +date: 2023-06-12 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 d2d405b372df1..00fd5f55835b2 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 62af6bbbcc171..a4499cffc0d2e 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -1,248 +1,7 @@ { "id": "cases", "client": { - "classes": [ - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin", - "type": "Class", - "tags": [], - "label": "CasesUiPlugin", - "description": [], - "signature": [ - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.CasesUiPlugin", - "text": "CasesUiPlugin" - }, - " implements ", - { - "pluginId": "@kbn/core-plugins-browser", - "scope": "common", - "docId": "kibKbnCorePluginsBrowserPluginApi", - "section": "def-common.Plugin", - "text": "Plugin" - }, - "" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.Unnamed", - "type": "Function", - "tags": [], - "label": "Constructor", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.Unnamed.$1", - "type": "Object", - "tags": [], - "label": "initializerContext", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-plugins-browser", - "scope": "common", - "docId": "kibKbnCorePluginsBrowserPluginApi", - "section": "def-common.PluginInitializerContext", - "text": "PluginInitializerContext" - }, - "" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.setup", - "type": "Function", - "tags": [], - "label": "setup", - "description": [], - "signature": [ - "(core: ", - { - "pluginId": "@kbn/core-lifecycle-browser", - "scope": "common", - "docId": "kibKbnCoreLifecycleBrowserPluginApi", - "section": "def-common.CoreSetup", - "text": "CoreSetup" - }, - ", plugins: ", - "CasesPluginSetup", - ") => ", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.CasesUiSetup", - "text": "CasesUiSetup" - } - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.setup.$1", - "type": "Object", - "tags": [], - "label": "core", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-lifecycle-browser", - "scope": "common", - "docId": "kibKbnCoreLifecycleBrowserPluginApi", - "section": "def-common.CoreSetup", - "text": "CoreSetup" - }, - "" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.setup.$2", - "type": "Object", - "tags": [], - "label": "plugins", - "description": [], - "signature": [ - "CasesPluginSetup" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.start", - "type": "Function", - "tags": [], - "label": "start", - "description": [], - "signature": [ - "(core: ", - { - "pluginId": "@kbn/core-lifecycle-browser", - "scope": "common", - "docId": "kibKbnCoreLifecycleBrowserPluginApi", - "section": "def-common.CoreStart", - "text": "CoreStart" - }, - ", plugins: ", - "CasesPluginStart", - ") => ", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.CasesUiStart", - "text": "CasesUiStart" - } - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.start.$1", - "type": "Object", - "tags": [], - "label": "core", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-lifecycle-browser", - "scope": "common", - "docId": "kibKbnCoreLifecycleBrowserPluginApi", - "section": "def-common.CoreStart", - "text": "CoreStart" - } - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.start.$2", - "type": "Object", - "tags": [], - "label": "plugins", - "description": [], - "signature": [ - "CasesPluginStart" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "cases", - "id": "def-public.CasesUiPlugin.stop", - "type": "Function", - "tags": [], - "label": "stop", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/cases/public/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false - } - ], + "classes": [], "functions": [ { "parentPluginId": "cases", @@ -279,39 +38,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "cases", - "id": "def-public.getCasesConfigurePath", - "type": "Function", - "tags": [], - "label": "getCasesConfigurePath", - "description": [], - "signature": [ - "(casesBasePath: string) => string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.getCasesConfigurePath.$1", - "type": "string", - "tags": [], - "label": "casesBasePath", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "cases", "id": "def-public.getCasesDeepLinks", @@ -402,72 +128,6 @@ ], "returnComment": [], "initialIsOpen": false - }, - { - "parentPluginId": "cases", - "id": "def-public.getCaseViewPath", - "type": "Function", - "tags": [], - "label": "getCaseViewPath", - "description": [], - "signature": [ - "(casesBasePath: string) => string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.getCaseViewPath.$1", - "type": "string", - "tags": [], - "label": "casesBasePath", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "cases", - "id": "def-public.getCreateCasePath", - "type": "Function", - "tags": [], - "label": "getCreateCasePath", - "description": [], - "signature": [ - "(casesBasePath: string) => string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "cases", - "id": "def-public.getCreateCasePath.$1", - "type": "string", - "tags": [], - "label": "casesBasePath", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/cases/public/common/navigation/paths.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false } ], "interfaces": [ @@ -510,13 +170,7 @@ "label": "CaseAttachments", "description": [], "signature": [ - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.SupportedCaseAttachment", - "text": "SupportedCaseAttachment" - }, + "SupportedCaseAttachment", "[]" ], "path": "x-pack/plugins/cases/public/types.ts", @@ -602,10 +256,10 @@ }, { "parentPluginId": "cases", - "id": "def-public.GetCasesProps", + "id": "def-public.GetCreateCaseFlyoutProps", "type": "Type", "tags": [], - "label": "GetCasesProps", + "label": "GetCreateCaseFlyoutProps", "description": [], "signature": [ "{ features?: Partial<", @@ -618,52 +272,17 @@ "section": "def-common.CasesPermissions", "text": "CasesPermissions" }, - "; onComponentInitialized?: (() => void) | undefined; actionsNavigation?: ", - "CasesNavigation", - " | undefined; ruleDetailsNavigation?: ", - "CasesNavigation", - " | undefined; showAlertDetails?: ((alertId: string, index: string) => void) | undefined; useFetchAlertData: ", - "UseFetchAlertData", - "; refreshRef?: React.MutableRefObject<", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CaseViewRefreshPropInterface", - "text": "CaseViewRefreshPropInterface" - }, - "> | undefined; timelineIntegration?: ", - "CasesTimelineIntegration", - " | undefined; basePath?: string | undefined; releasePhase?: ", + "; basePath?: string | undefined; releasePhase?: ", "ReleasePhase", - " | undefined; }" - ], - "path": "x-pack/plugins/cases/public/client/ui/get_cases.tsx", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "cases", - "id": "def-public.GetCreateCaseFlyoutProps", - "type": "Type", - "tags": [], - "label": "GetCreateCaseFlyoutProps", - "description": [], - "signature": [ - "{ features?: Partial<", - "CasesContextFeatures", - "> | undefined; owner: string[]; permissions: ", + " | undefined; onClose?: (() => void) | undefined; attachments?: ", { "pluginId": "cases", - "scope": "common", + "scope": "public", "docId": "kibCasesPluginApi", - "section": "def-common.CasesPermissions", - "text": "CasesPermissions" + "section": "def-public.CaseAttachmentsWithoutOwner", + "text": "CaseAttachmentsWithoutOwner" }, - "; basePath?: string | undefined; releasePhase?: ", - "ReleasePhase", - " | undefined; onClose?: (() => void) | undefined; afterCaseCreated?: ((theCase: ", + " | undefined; afterCaseCreated?: ((theCase: ", { "pluginId": "cases", "scope": "common", @@ -693,15 +312,7 @@ "section": "def-common.CaseUI", "text": "CaseUI" }, - ") => void) | undefined; attachments?: ", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.CaseAttachmentsWithoutOwner", - "text": "CaseAttachmentsWithoutOwner" - }, - " | undefined; headerContent?: React.ReactNode; initialValue?: Pick<{ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", + ") => void) | undefined; headerContent?: React.ReactNode; initialValue?: Pick<{ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", { "pluginId": "cases", "scope": "common", @@ -813,101 +424,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "cases", - "id": "def-public.SupportedCaseAttachment", - "type": "Type", - "tags": [], - "label": "SupportedCaseAttachment", - "description": [], - "signature": [ - "{ comment: string; type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CommentType", - "text": "CommentType" - }, - ".user; owner: string; } | { type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CommentType", - "text": "CommentType" - }, - ".alert; alertId: string | string[]; index: string | string[]; rule: { id: string | null; name: string | null; }; owner: string; } | { externalReferenceId: string; externalReferenceStorage: { type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.ExternalReferenceStorageType", - "text": "ExternalReferenceStorageType" - }, - ".elasticSearchDoc; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.JsonValue", - "text": "JsonValue" - }, - "; } | null; type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CommentType", - "text": "CommentType" - }, - ".externalReference; owner: string; } | { externalReferenceId: string; externalReferenceStorage: { type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.ExternalReferenceStorageType", - "text": "ExternalReferenceStorageType" - }, - ".savedObject; soType: string; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.JsonValue", - "text": "JsonValue" - }, - "; } | null; type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CommentType", - "text": "CommentType" - }, - ".externalReference; owner: string; } | { type: ", - { - "pluginId": "cases", - "scope": "common", - "docId": "kibCasesPluginApi", - "section": "def-common.CommentType", - "text": "CommentType" - }, - ".persistableState; owner: string; persistableStateAttachmentTypeId: string; persistableStateAttachmentState: { [x: string]: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.JsonValue", - "text": "JsonValue" - }, - "; }; }" - ], - "path": "x-pack/plugins/cases/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "objects": [ @@ -1153,21 +669,9 @@ "description": [], "signature": [ "{ getCases: (props: ", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.GetCasesProps", - "text": "GetCasesProps" - }, + "GetCasesProps", ") => React.ReactElement<", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.GetCasesProps", - "text": "GetCasesProps" - }, + "GetCasesProps", ", string | React.JSXElementConstructor>; getCasesContext: () => React.FC<", "GetCasesContextProps", ">; getAllCasesSelectorModal: (props: ", @@ -1186,22 +690,6 @@ "section": "def-public.GetAllCasesSelectorModalProps", "text": "GetAllCasesSelectorModalProps" }, - ", string | React.JSXElementConstructor>; getCreateCaseFlyout: (props: ", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.GetCreateCaseFlyoutProps", - "text": "GetCreateCaseFlyoutProps" - }, - ") => React.ReactElement<", - { - "pluginId": "cases", - "scope": "public", - "docId": "kibCasesPluginApi", - "section": "def-public.GetCreateCaseFlyoutProps", - "text": "GetCreateCaseFlyoutProps" - }, ", string | React.JSXElementConstructor>; getRecentCases: (props: ", { "pluginId": "cases", @@ -1602,27 +1090,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "cases", - "id": "def-common.getAllConnectorsUrl", - "type": "Function", - "tags": [], - "label": "getAllConnectorsUrl", - "description": [ - "\n" - ], - "signature": [ - "() => string" - ], - "path": "x-pack/plugins/cases/common/utils/connectors_api.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [ - "All connectors endpoint" - ], - "initialIsOpen": false - }, { "parentPluginId": "cases", "id": "def-common.getApiTags", @@ -1689,27 +1156,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "cases", - "id": "def-common.getCreateConnectorUrl", - "type": "Function", - "tags": [], - "label": "getCreateConnectorUrl", - "description": [ - "\n" - ], - "signature": [ - "() => string" - ], - "path": "x-pack/plugins/cases/common/utils/connectors_api.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [ - "Create connector endpoint" - ], - "initialIsOpen": false - }, { "parentPluginId": "cases", "id": "def-common.throwErrors", @@ -2345,21 +1791,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "cases", - "id": "def-common.CasesBulkGetRequest", - "type": "Type", - "tags": [], - "label": "CasesBulkGetRequest", - "description": [], - "signature": [ - "{ ids: string[]; }" - ], - "path": "x-pack/plugins/cases/common/api/cases/case.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "cases", "id": "def-common.CasesBulkGetResponse", @@ -2535,23 +1966,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "cases", - "id": "def-common.CasesFeatures", - "type": "Type", - "tags": [], - "label": "CasesFeatures", - "description": [], - "signature": [ - "{ alerts?: { sync?: boolean | undefined; enabled?: boolean | undefined; isExperimental?: boolean | undefined; } | undefined; metrics?: ", - "SingleCaseMetricsFeature", - "[] | undefined; }" - ], - "path": "x-pack/plugins/cases/common/ui/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "cases", "id": "def-common.CasesFindResponseUI", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 48133a0832a44..c6956350e1d76 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 101 | 0 | 84 | 30 | +| 79 | 0 | 64 | 26 | ## Client @@ -34,9 +34,6 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o ### Functions -### Classes - - ### Interfaces diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 82146bc810a6f..a3eef7bdb3213 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-06-09 +date: 2023-06-12 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 c816643f9259b..d2af72b90d487 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-06-09 +date: 2023-06-12 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 ca4bbc29f0fff..abe51adf0ae6e 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 036a19de16340..938083d764d5b 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-06-09 +date: 2023-06-12 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 e5d51bc73b9e8..d6839db103a4c 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-06-09 +date: 2023-06-12 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 6b2c2e819ea36..e8cc57ca19edc 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-06-09 +date: 2023-06-12 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 2527987f6e263..48997ec082d69 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-06-09 +date: 2023-06-12 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 734817e0ffb8d..25886d6ddae2d 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-06-09 +date: 2023-06-12 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 b49183f369942..1cff2b2b88d9c 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.devdocs.json b/api_docs/controls.devdocs.json index b8a9070af8ed5..41bad0333fa5c 100644 --- a/api_docs/controls.devdocs.json +++ b/api_docs/controls.devdocs.json @@ -6092,15 +6092,15 @@ "section": "def-common.RawControlGroupAttributes", "text": "RawControlGroupAttributes" }, - ") => Omit<", + ") => ", { "pluginId": "controls", "scope": "common", "docId": "kibControlsPluginApi", - "section": "def-common.ControlGroupInput", - "text": "ControlGroupInput" + "section": "def-common.PersistableControlGroupInput", + "text": "PersistableControlGroupInput" }, - ", \"id\"> | undefined" + " | undefined" ], "path": "src/plugins/controls/common/control_group/control_group_persistence.ts", "deprecated": false, diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 7155051498188..cdf1a906d02a3 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-06-09 +date: 2023-06-12 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 5eddcbe0e7c9a..55f709ad13bd4 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.devdocs.json b/api_docs/dashboard.devdocs.json index 50f0cb0ecdd8a..3ad16cf6bb29d 100644 --- a/api_docs/dashboard.devdocs.json +++ b/api_docs/dashboard.devdocs.json @@ -343,7 +343,7 @@ "description": [], "signature": [ "((result: ", - "LoadDashboardFromSavedObjectReturn", + "LoadDashboardReturn", ") => boolean) | undefined" ], "path": "src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx", @@ -358,7 +358,7 @@ "label": "result", "description": [], "signature": [ - "LoadDashboardFromSavedObjectReturn" + "LoadDashboardReturn" ], "path": "src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx", "deprecated": false, @@ -507,13 +507,7 @@ "text": "DashboardContainerInput" }, ", \"executionContext\" | \"panels\" | \"controlGroupInput\" | \"isEmbeddedExternally\">> & { dashboardId?: string | undefined; useHash?: boolean | undefined; preserveSavedFilters?: boolean | undefined; searchSessionId?: string | undefined; panels?: (", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + "SavedDashboardPanel", " & ", { "pluginId": "@kbn/utility-types", @@ -667,80 +661,7 @@ }, "server": { "classes": [], - "functions": [ - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables", - "type": "Function", - "tags": [], - "label": "findByValueEmbeddables", - "description": [], - "signature": [ - "(savedObjectClient: Pick<", - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.ISavedObjectsRepository", - "text": "ISavedObjectsRepository" - }, - ", \"find\">, embeddableType: string) => Promise<{ [key: string]: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.Serializable", - "text": "Serializable" - }, - "; }[]>" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables.$1", - "type": "Object", - "tags": [], - "label": "savedObjectClient", - "description": [], - "signature": [ - "Pick<", - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.ISavedObjectsRepository", - "text": "ISavedObjectsRepository" - }, - ", \"find\">" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "dashboard", - "id": "def-server.findByValueEmbeddables.$2", - "type": "string", - "tags": [], - "label": "embeddableType", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], + "functions": [], "interfaces": [], "enums": [], "misc": [], @@ -793,14 +714,8 @@ "section": "def-common.DashboardPanelMap", "text": "DashboardPanelMap" }, - ", version: string) => ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + ", versionOverride?: string | undefined) => ", + "SavedDashboardPanel", "[]" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", @@ -833,15 +748,15 @@ "id": "def-common.convertPanelMapToSavedPanels.$2", "type": "string", "tags": [], - "label": "version", + "label": "versionOverride", "description": [], "signature": [ - "string" + "string | undefined" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false } ], "returnComment": [], @@ -871,14 +786,8 @@ "section": "def-common.SavedObjectEmbeddableInput", "text": "SavedObjectEmbeddableInput" }, - ">, version: string) => ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - } + ">, version: string | undefined) => ", + "SavedDashboardPanel" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, @@ -922,12 +831,12 @@ "label": "version", "description": [], "signature": [ - "string" + "string | undefined" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false } ], "returnComment": [], @@ -942,13 +851,7 @@ "description": [], "signature": [ "(savedDashboardPanel: ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + "SavedDashboardPanel", ") => ", { "pluginId": "dashboard", @@ -971,13 +874,7 @@ "label": "savedDashboardPanel", "description": [], "signature": [ - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - } + "SavedDashboardPanel" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", "deprecated": false, @@ -997,13 +894,7 @@ "description": [], "signature": [ "(panels?: ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + "SavedDashboardPanel", "[] | undefined) => ", { "pluginId": "dashboard", @@ -1025,13 +916,7 @@ "label": "panels", "description": [], "signature": [ - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + "SavedDashboardPanel", "[] | undefined" ], "path": "src/plugins/dashboard/common/lib/dashboard_panel_converters.ts", @@ -1192,9 +1077,12 @@ "label": "extractReferences", "description": [], "signature": [ - "({ attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ", - "ExtractDeps", - ") => SavedObjectAttributesAndReferences" + "({ attributes, references = [] }: ", + "DashboardAttributesAndReferences", + ", deps: ", + "InjectExtractDeps", + ") => ", + "DashboardAttributesAndReferences" ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", "deprecated": false, @@ -1208,7 +1096,7 @@ "label": "{ attributes, references = [] }", "description": [], "signature": [ - "SavedObjectAttributesAndReferences" + "DashboardAttributesAndReferences" ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", "deprecated": false, @@ -1223,7 +1111,7 @@ "label": "deps", "description": [], "signature": [ - "ExtractDeps" + "InjectExtractDeps" ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", "deprecated": false, @@ -1242,15 +1130,17 @@ "label": "injectReferences", "description": [], "signature": [ - "({ attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ", - "InjectDeps", + "({ attributes, references = [] }: ", + "DashboardAttributesAndReferences", + ", deps: ", + "InjectExtractDeps", ") => ", { - "pluginId": "@kbn/core-saved-objects-common", + "pluginId": "dashboard", "scope": "common", - "docId": "kibKbnCoreSavedObjectsCommonPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" + "docId": "kibDashboardPluginApi", + "section": "def-common.DashboardAttributes", + "text": "DashboardAttributes" } ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", @@ -1265,7 +1155,7 @@ "label": "{ attributes, references = [] }", "description": [], "signature": [ - "SavedObjectAttributesAndReferences" + "DashboardAttributesAndReferences" ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", "deprecated": false, @@ -1280,7 +1170,7 @@ "label": "deps", "description": [], "signature": [ - "InjectDeps" + "InjectExtractDeps" ], "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts", "deprecated": false, @@ -1293,189 +1183,6 @@ } ], "interfaces": [ - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes", - "type": "Interface", - "tags": [], - "label": "DashboardAttributes", - "description": [ - "\nThe attributes of the dashboard saved object. This interface should be the\nsource of truth for the latest dashboard attributes shape after all migrations." - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.controlGroupInput", - "type": "CompoundType", - "tags": [], - "label": "controlGroupInput", - "description": [], - "signature": [ - { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.RawControlGroupAttributes", - "text": "RawControlGroupAttributes" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.refreshInterval", - "type": "Object", - "tags": [], - "label": "refreshInterval", - "description": [], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.RefreshInterval", - "text": "RefreshInterval" - }, - " | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.timeRestore", - "type": "boolean", - "tags": [], - "label": "timeRestore", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.optionsJSON", - "type": "string", - "tags": [], - "label": "optionsJSON", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.useMargins", - "type": "CompoundType", - "tags": [], - "label": "useMargins", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.panelsJSON", - "type": "string", - "tags": [], - "label": "panelsJSON", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.timeFrom", - "type": "string", - "tags": [], - "label": "timeFrom", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.version", - "type": "number", - "tags": [], - "label": "version", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.timeTo", - "type": "string", - "tags": [], - "label": "timeTo", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.title", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.DashboardAttributes.kibanaSavedObjectMeta", - "type": "Object", - "tags": [], - "label": "kibanaSavedObjectMeta", - "description": [], - "signature": [ - "{ searchSourceJSON: string; }" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "dashboard", "id": "def-common.DashboardCapabilities", @@ -2021,13 +1728,7 @@ "label": "gridData", "description": [], "signature": [ - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.GridData", - "text": "GridData" - } + "GridData" ], "path": "src/plugins/dashboard/common/dashboard_container/types.ts", "deprecated": false, @@ -2049,214 +1750,41 @@ } ], "initialIsOpen": false - }, + } + ], + "enums": [], + "misc": [ { "parentPluginId": "dashboard", - "id": "def-common.GridData", - "type": "Interface", + "id": "def-common.DashboardAttributes", + "type": "Type", "tags": [], - "label": "GridData", - "description": [ - "\nGrid type for React Grid Layout" - ], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.GridData.w", - "type": "number", - "tags": [], - "label": "w", - "description": [], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.GridData.h", - "type": "number", - "tags": [], - "label": "h", - "description": [], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, + "label": "DashboardAttributes", + "description": [], + "signature": [ + "{ controlGroupInput?: ", { - "parentPluginId": "dashboard", - "id": "def-common.GridData.x", - "type": "number", - "tags": [], - "label": "x", - "description": [], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.RawControlGroupAttributes", + "text": "RawControlGroupAttributes" }, + " | undefined; refreshInterval?: ", { - "parentPluginId": "dashboard", - "id": "def-common.GridData.y", - "type": "number", - "tags": [], - "label": "y", - "description": [], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.RefreshInterval", + "text": "RefreshInterval" }, - { - "parentPluginId": "dashboard", - "id": "def-common.GridData.i", - "type": "string", - "tags": [], - "label": "i", - "description": [], - "path": "src/plugins/dashboard/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel", - "type": "Interface", - "tags": [], - "label": "SavedDashboardPanel", - "description": [ - "\nA saved dashboard panel parsed directly from the Dashboard Attributes panels JSON" + " | undefined; timeRestore: boolean; optionsJSON?: string | undefined; useMargins?: boolean | undefined; description: string; panelsJSON: string; timeFrom?: string | undefined; version: number; timeTo?: string | undefined; title: string; kibanaSavedObjectMeta: { searchSourceJSON: string; }; }" ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", + "path": "src/plugins/dashboard/common/content_management/v1/types.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.embeddableConfig", - "type": "Object", - "tags": [], - "label": "embeddableConfig", - "description": [], - "signature": [ - "{ [key: string]: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.Serializable", - "text": "Serializable" - }, - "; }" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.panelRefName", - "type": "string", - "tags": [], - "label": "panelRefName", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.gridData", - "type": "Object", - "tags": [], - "label": "gridData", - "description": [], - "signature": [ - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.GridData", - "text": "GridData" - } - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.panelIndex", - "type": "string", - "tags": [], - "label": "panelIndex", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.version", - "type": "string", - "tags": [], - "label": "version", - "description": [], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "dashboard", - "id": "def-common.SavedDashboardPanel.title", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], "initialIsOpen": false - } - ], - "enums": [], - "misc": [ + }, { "parentPluginId": "dashboard", "id": "def-common.DashboardContainerByReferenceInput", @@ -2278,45 +1806,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "dashboard", - "id": "def-common.ParsedDashboardAttributes", - "type": "Type", - "tags": [], - "label": "ParsedDashboardAttributes", - "description": [], - "signature": [ - "Omit<", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.DashboardAttributes", - "text": "DashboardAttributes" - }, - ", \"panelsJSON\" | \"optionsJSON\"> & { panels: ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, - "[]; options: ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.DashboardOptions", - "text": "DashboardOptions" - }, - "; }" - ], - "path": "src/plugins/dashboard/common/dashboard_saved_object/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "dashboard", "id": "def-common.SharedDashboardState", @@ -2392,13 +1881,7 @@ "text": "PersistableControlGroupInput" }, " | undefined; isEmbeddedExternally?: boolean | undefined; timeRestore?: boolean | undefined; useMargins?: boolean | undefined; panels?: ", - { - "pluginId": "dashboard", - "scope": "common", - "docId": "kibDashboardPluginApi", - "section": "def-common.SavedDashboardPanel", - "text": "SavedDashboardPanel" - }, + "SavedDashboardPanel", "[] | undefined; }" ], "path": "src/plugins/dashboard/common/types.ts", diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 2bb9d9ed1d51f..6cdf7b1a1dd50 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 130 | 0 | 125 | 7 | +| 99 | 0 | 97 | 9 | ## Client @@ -48,9 +48,6 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib ### Start -### Functions - - ## Common ### Objects diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 36e93b24900a1..709f076f610ea 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 025daaa4ff190..f825ce4b8ead8 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 2dfcca5800b08..335d0f10bee49 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 25f9b993de236..649ef6df37f7d 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 03f9ac2a041e1..6ead8a9c462fb 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-06-09 +date: 2023-06-12 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 f156fc0495f1b..a0808c1369797 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-06-09 +date: 2023-06-12 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 5112eb16761eb..56a7cdda3f8b0 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index b147b37c79476..472ad034ec099 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 4e492765dc899..c298865965fe2 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-06-09 +date: 2023-06-12 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 57fe2673ddfb9..0a0c4edbc0592 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -25,25 +25,27 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | encryptedSavedObjects, actions, data, ml, securitySolution, logstash, cloudChat | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | | | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, eventAnnotation, lens, fileUpload, ml, canvas, dashboardEnhanced, monitoring, synthetics, transform, discover, dataVisualizer | - | -| | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, home, savedObjects, visualizations, dashboard, eventAnnotation, ml, canvas, lens, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | +| | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, home, savedObjects, visualizations, eventAnnotation, ml, canvas, lens, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core-saved-objects-browser-mocks, savedObjects, presentationUtil, dashboard, eventAnnotation, ml, dashboardEnhanced, @kbn/core-saved-objects-browser-internal | - | | | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, dashboard, eventAnnotation, ml, dashboardEnhanced, monitoring, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjects, embeddable, presentationUtil, visualizations, dashboard, aiops, ml, dataVisualizer, dashboardEnhanced, graph, synthetics, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | -| | @kbn/core-lifecycle-browser-mocks, @kbn/core, ml, dashboard, dataViews, @kbn/core-plugins-browser-internal | - | -| | @kbn/core, savedObjects, embeddable, visualizations, dashboard, canvas, graph, ml, @kbn/core-saved-objects-common, @kbn/core-saved-objects-server, actions, alerting, savedSearch, enterpriseSearch, securitySolution, taskManager, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-server | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjects, embeddable, presentationUtil, visualizations, aiops, ml, dataVisualizer, dashboardEnhanced, graph, synthetics, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | +| | @kbn/core-lifecycle-browser-mocks, @kbn/core, ml, dataViews, @kbn/core-plugins-browser-internal | - | +| | @kbn/core, savedObjects, embeddable, visualizations, canvas, graph, ml, @kbn/core-saved-objects-common, @kbn/core-saved-objects-server, actions, alerting, savedSearch, enterpriseSearch, securitySolution, taskManager, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-server | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | | | infra, graph, stackAlerts, inputControlVis, securitySolution, savedObjects | - | | | dashboard, dataVisualizer, stackAlerts, expressionPartitionVis | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | +| | observability, @kbn/securitysolution-data-table, securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core, savedObjects, savedObjectsManagement, visualizations, eventAnnotation, savedObjectsTagging, lens, graph, dashboard, savedObjectsTaggingOss, kibanaUtils, expressions, dataViews, data, embeddable, controls, uiActionsEnhanced, cases, maps, canvas, dashboardEnhanced, globalSearchProviders, infra | - | | | alerting, discover, securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, home, fleet, securitySolution, graph, lists, alerting | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, home, fleet, securitySolution, graph, lists, alerting | - | | | alerting, discover, securitySolution | - | | | securitySolution | - | +| | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | -| | @kbn/securitysolution-data-table, securitySolution | - | -| | @kbn/securitysolution-data-table, securitySolution | - | +| | securitySolution, @kbn/securitysolution-data-table | - | +| | securitySolution, @kbn/securitysolution-data-table | - | | | securitySolution | - | | | securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-api-server, @kbn/core, home, data, savedObjectsTagging, canvas, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-import-export-server-internal, savedObjectsTaggingOss, securitySolution, lists, synthetics, upgradeAssistant, savedObjectsManagement, @kbn/core-ui-settings-server-internal | - | @@ -66,13 +68,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | actions, alerting | - | | | discover | - | | | data, discover, imageEmbeddable, embeddable | - | -| | @kbn/core-saved-objects-browser-mocks, dataViews, dashboard, discover, @kbn/core-saved-objects-browser-internal | - | +| | @kbn/core-saved-objects-browser-mocks, dataViews, discover, @kbn/core-saved-objects-browser-internal | - | | | advancedSettings, discover | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, savedObjects, dashboard, eventAnnotation | - | | | @kbn/core-saved-objects-browser-mocks, home, eventAnnotation, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, savedObjects, visualizations, dashboard | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, savedObjects, visualizations | - | | | @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-browser-mocks, dashboard, savedObjects, @kbn/core-saved-objects-browser-internal | - | +| | @kbn/core-saved-objects-browser-mocks, savedObjects, @kbn/core-saved-objects-browser-internal | - | | | @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-browser-internal | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, eventAnnotation, savedObjects | - | | | @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-browser-internal | - | @@ -85,7 +87,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core, spaces, savedSearch, visualizations, dashboard, lens, cases, maps, canvas, graph | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core, spaces, savedSearch, visualizations, lens, cases, maps, canvas, graph | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 373b53601560b..bdf3a13a27cb7 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -300,6 +300,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedCellValueElementProps), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedCellValueElementProps) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedRowRenderer), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedRowRenderer) | - | | | [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField) | - | | | [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [mock_source.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/mock/mock_source.ts#:~:text=BrowserFields), [mock_source.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/mock/mock_source.ts#:~:text=BrowserFields) | - | @@ -471,20 +473,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/types.ts#:~:text=fieldFormats), [data_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/data_service.ts#:~:text=fieldFormats) | - | | | [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_container/embeddable/api/overlays/save_modal.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_container/embeddable/api/overlays/save_modal.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | | | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=SavedObject) | 8.8.0 | -| | [dashboard_saved_object_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts#:~:text=savedObjects), [index.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/index.ts#:~:text=savedObjects) | - | -| | [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectsClientContract), [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectsClientContract), [save_dashboard_state_to_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts#:~:text=SavedObjectsClientContract), [save_dashboard_state_to_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts#:~:text=SavedObjectsClientContract), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SavedObjectsClientContract), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SavedObjectsClientContract), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SavedObjectsClientContract), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SavedObjectsClientContract), [check_for_duplicate_dashboard_title.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts#:~:text=SavedObjectsClientContract), [check_for_duplicate_dashboard_title.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts#:~:text=SavedObjectsClientContract)+ 4 more | - | -| | [save_dashboard_state_to_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts#:~:text=create), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=create) | - | -| | [dashboard_listing.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx#:~:text=delete) | - | -| | [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=find), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=find), [check_for_duplicate_dashboard_title.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts#:~:text=find), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=find) | - | +| | [index.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/index.ts#:~:text=savedObjects) | - | +| | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=create) | - | +| | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=find) | - | | | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=get) | - | -| | [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=bulkGet) | - | -| | [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=resolve) | - | -| | [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SimpleSavedObject), [find_dashboard_saved_objects.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts#:~:text=SimpleSavedObject), [dashboard_listing.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx#:~:text=SimpleSavedObject), [dashboard_listing.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx#:~:text=SimpleSavedObject) | - | -| | [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=ResolvedSimpleSavedObject), [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=ResolvedSimpleSavedObject) | - | -| | [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [migrate_extract_panel_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry_collection_task.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [dashboard_telemetry.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/dashboard_telemetry.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [find_by_value_embeddables.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts#:~:text=SavedObjectAttributes), [load_dashboard_state_from_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts#:~:text=SavedObjectAttributes)+ 20 more | - | | | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=SavedObjectsStart), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx#:~:text=SavedObjectsStart) | - | -| | [dashboard_saved_object.stub.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts#:~:text=savedObjectsServiceMock), [dashboard_saved_object.stub.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts#:~:text=savedObjectsServiceMock) | - | -| | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/bwc/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/bwc/types.ts#:~:text=SavedObjectReference), [dashboard_saved_object_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts#:~:text=SavedObjectReference), [dashboard_saved_object_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts#:~:text=SavedObjectReference), [dashboard_saved_object_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts#:~:text=SavedObjectReference), [dashboard_container_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts#:~:text=SavedObjectReference), [dashboard_container_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts#:~:text=SavedObjectReference), [dashboard_container_references.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts#:~:text=SavedObjectReference) | - | +| | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/bwc/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/common/bwc/types.ts#:~:text=SavedObjectReference) | - | | | [dashboard_saved_object.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | @@ -941,8 +935,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts#:~:text=alertFactory), [executor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts#:~:text=alertFactory) | - | +| | [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts#:~:text=alertFactory), [threshold_executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts#:~:text=alertFactory), [executor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts#:~:text=alertFactory) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks) | - | +| | [render_cell_value.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/alerts_table/render_cell_value.tsx#:~:text=DeprecatedCellValueElementProps), [render_cell_value.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/alerts_table/render_cell_value.tsx#:~:text=DeprecatedCellValueElementProps) | - | @@ -1138,9 +1133,11 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [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 | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [open_close_signals_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedCellValueElementProps), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedCellValueElementProps) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [use_data_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx#:~:text=BrowserField)+ 31 more | - | -| | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 102 more | - | +| | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 102 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/types.ts#:~:text=SimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/types.ts#:~:text=SimpleSavedObject) | - | @@ -1153,7 +1150,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 32 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION) | - | -| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 16 more | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 14 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION) | - | | | [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID)+ 14 more | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index c9f16548d14db..a66b5f99dd868 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 44963da5047a4..cf16333ba96ec 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-06-09 +date: 2023-06-12 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 ff872910f8ff3..c6a6987adbab8 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-06-09 +date: 2023-06-12 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 a55ee7375d400..f7362fab6d283 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-06-09 +date: 2023-06-12 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 cd091ccd541c5..c07d6e9f996ec 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index ead5399485a42..c41582e280d13 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -9839,6 +9839,20 @@ "path": "src/plugins/embeddable/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.PanelState.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/embeddable/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -12136,6 +12150,20 @@ "path": "src/plugins/embeddable/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-common.PanelState.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/embeddable/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 98e5c5dea2523..95e318bad04e0 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 546 | 11 | 442 | 4 | +| 548 | 11 | 444 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 2b87751ed5f10..001d5d1030b52 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-06-09 +date: 2023-06-12 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 be3d8b8bbb179..4df2879cea744 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-06-09 +date: 2023-06-12 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 942605881b116..396e0cfa0553b 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-06-09 +date: 2023-06-12 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 82c46a2bebced..c49110fa878b8 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-06-09 +date: 2023-06-12 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 cd02194e457f7..1b8f829801eeb 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'essSecurity'] --- import essSecurityObj from './ess_security.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index eea372db3dfd9..a994b96882213 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 9bdbbd26e7fd2..6395d8dbc17fc 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-06-09 +date: 2023-06-12 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 8fa61e660f925..87f3c6ec627c9 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-06-09 +date: 2023-06-12 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 85cd1ac7998a0..9abaf4d7b5d1d 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-06-09 +date: 2023-06-12 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 d294690020ccc..bddad6cb28535 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-06-09 +date: 2023-06-12 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 eb0d350925297..93941c304a38f 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-06-09 +date: 2023-06-12 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 817e4bce8fefc..2b40a9874747c 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-06-09 +date: 2023-06-12 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 a788fadcfa1cd..1f4fb8c368306 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-06-09 +date: 2023-06-12 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 9a84b4c368876..8d7aa4b693a3f 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-06-09 +date: 2023-06-12 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 62d7bfee2765c..5eab3d8402f0b 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-06-09 +date: 2023-06-12 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 6f29b6ebd596c..66c5003a95d62 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-06-09 +date: 2023-06-12 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 9de3b6df260bb..fd9a1553e3bdc 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-06-09 +date: 2023-06-12 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 7de84b4a4f0c3..8b46ac771cb9b 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-06-09 +date: 2023-06-12 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 ddd23e564c5c0..b177f8b0110a5 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-06-09 +date: 2023-06-12 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 96b6f39c15a57..1caa8b95f0d6d 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-06-09 +date: 2023-06-12 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 37ecc718169ee..f938ff34c2ad3 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-06-09 +date: 2023-06-12 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 784f0aaf154d5..ee8b62b15b3a0 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-06-09 +date: 2023-06-12 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 b8e1e046787c8..c1bd1d32348e9 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-06-09 +date: 2023-06-12 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 2ab3bda959f7c..dab761f6af6ad 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-06-09 +date: 2023-06-12 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 e98c1e3c0fdcc..6257bb919c41a 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 39e99cd40894b..392c2687a8118 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -577,10 +577,6 @@ "plugin": "cases", "path": "x-pack/plugins/cases/public/plugin.ts" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/plugin.ts" - }, { "plugin": "imageEmbeddable", "path": "src/plugins/image_embeddable/public/plugin.ts" diff --git a/api_docs/files.mdx b/api_docs/files.mdx index e0ee9e1caa420..36a0c263daf61 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-06-09 +date: 2023-06-12 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 c39c958c64433..c3acc938f684a 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 9919ccb784fbd..26e4b61987c70 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -17059,7 +17059,7 @@ "\nReturns the index name for File data (chunks) storage for a given integration" ], "signature": [ - "(integrationName: string) => string" + "(integrationName: string, forHostDelivery?: boolean) => string" ], "path": "x-pack/plugins/fleet/common/services/file_storage.ts", "deprecated": false, @@ -17079,6 +17079,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-common.getFileDataIndexName.$2", + "type": "boolean", + "tags": [], + "label": "forHostDelivery", + "description": [], + "signature": [ + "boolean" + ], + "path": "x-pack/plugins/fleet/common/services/file_storage.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [], @@ -17094,7 +17109,7 @@ "\nReturns the index name for File Metadata storage for a given integration" ], "signature": [ - "(integrationName: string) => string" + "(integrationName: string, forHostDelivery?: boolean) => string" ], "path": "x-pack/plugins/fleet/common/services/file_storage.ts", "deprecated": false, @@ -17114,6 +17129,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-common.getFileMetadataIndexName.$2", + "type": "boolean", + "tags": [], + "label": "forHostDelivery", + "description": [], + "signature": [ + "boolean" + ], + "path": "x-pack/plugins/fleet/common/services/file_storage.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [], diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 85702673b6d83..4599099a5320d 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1182 | 3 | 1066 | 33 | +| 1184 | 3 | 1068 | 33 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index dde0e72deccc4..cdb1fe46626b4 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-06-09 +date: 2023-06-12 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 66f1a2f597b78..e2351757ad69d 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-06-09 +date: 2023-06-12 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 e1a77a35368cd..b43f767b62df6 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-06-09 +date: 2023-06-12 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 ae8fb8a6952d0..e6c0cb22cbc49 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-06-09 +date: 2023-06-12 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 7c44fcfa422c7..743b84af2e559 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-06-09 +date: 2023-06-12 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 683680c9b9f75..b294c6121b422 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-06-09 +date: 2023-06-12 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 6c470eef7998a..b0c73d21e5841 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-06-09 +date: 2023-06-12 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 a093eda8d82ea..a609a035373e7 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-06-09 +date: 2023-06-12 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 ddcd86d6a61a4..e3365e393efa0 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-06-09 +date: 2023-06-12 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 4495a240671ce..618b941eedce0 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-06-09 +date: 2023-06-12 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 717d1c23ffd26..73dd297db7aee 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-06-09 +date: 2023-06-12 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 3e8454f50e9f6..f4363a465d1e1 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-06-09 +date: 2023-06-12 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 3fc7461cd051f..0bbaedd1a7822 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-06-09 +date: 2023-06-12 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 e75697e32a6f2..b66d254dd3c60 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-06-09 +date: 2023-06-12 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 8b6ad88c5380d..313e8bc7b2bbc 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-06-09 +date: 2023-06-12 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 2e762ab5f2eee..48071daada839 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-06-09 +date: 2023-06-12 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 8397a839409f9..4309eb883bece 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-06-09 +date: 2023-06-12 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 ce926cfbba047..d5b50c92ea6ae 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-06-09 +date: 2023-06-12 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 9b771e4e06eb7..eb544818c5ddc 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-06-09 +date: 2023-06-12 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 8b50c9ff34a37..21d8260e7cb8a 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-06-09 +date: 2023-06-12 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 1411adaeaf2f0..1d96f7c3e34cd 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-06-09 +date: 2023-06-12 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 273841f4f7c04..b9dafa458573a 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-06-09 +date: 2023-06-12 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 11cfead321af2..5bf35473ac5e8 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-06-09 +date: 2023-06-12 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 a0ac63213dd7d..81d0e87e878f4 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-06-09 +date: 2023-06-12 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 712d90e4c8adb..4b5cfff486260 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-06-09 +date: 2023-06-12 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 13f798804549b..10ccc1aefdfac 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-06-09 +date: 2023-06-12 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 cbe241fe4bf40..530e065b421db 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-06-09 +date: 2023-06-12 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 78d9e9479e143..e35830797c055 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-06-09 +date: 2023-06-12 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 c0f4e5093e8ad..f91f8e6e6e312 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-06-09 +date: 2023-06-12 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 16d7a10319d05..2f161285b0bcd 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-06-09 +date: 2023-06-12 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 bf23c201491b2..21c042814cb4a 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-06-09 +date: 2023-06-12 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 272525a7093e4..40384cf7990e4 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-06-09 +date: 2023-06-12 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 8600c53d4762d..744f4f346eabd 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-06-09 +date: 2023-06-12 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 83b722a9e915c..79c91f67b2b70 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-06-09 +date: 2023-06-12 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 66b148dc78273..5ec807b0c5df1 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-06-09 +date: 2023-06-12 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 09918a9a01bc4..c574d64beb548 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-06-09 +date: 2023-06-12 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 dc438bb5ab30b..707e1b668855f 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-06-09 +date: 2023-06-12 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 4b4114c69af1b..e23cf2abe1df5 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-06-09 +date: 2023-06-12 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 1d3a0e118fe33..150949e1e4050 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-06-09 +date: 2023-06-12 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 6e66c1a619bd4..29c425878ce15 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-06-09 +date: 2023-06-12 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 f89388c139a81..32d65411d661b 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-06-09 +date: 2023-06-12 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 f0dc5fa93af8c..63d2269daf113 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index e222fe26caafc..9ba5cda5fe80a 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2023-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 2a9e00103a690..cef1b77bd56ed 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-06-09 +date: 2023-06-12 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 48351362195b0..9ef5e0d76dd1e 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-06-09 +date: 2023-06-12 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 2992dc417931d..b805fd916b4eb 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-06-09 +date: 2023-06-12 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 a9cb31e484a39..10834db3f26d6 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-06-09 +date: 2023-06-12 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 ab5476e531f9d..fb72ef35d4f67 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-06-09 +date: 2023-06-12 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 f80393fed61a9..0f312b9bf277e 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-06-09 +date: 2023-06-12 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 26e84d499ac88..345db5f4af50b 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-06-09 +date: 2023-06-12 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 a22e74b96e6dc..67bb299adf892 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-06-09 +date: 2023-06-12 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 20771768bef32..152708896a633 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-06-09 +date: 2023-06-12 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 741287ab871e4..960f725a91c04 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-06-09 +date: 2023-06-12 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 4ad60cbe88dbd..843611e72ad3c 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-06-09 +date: 2023-06-12 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 6d447740ba839..ed716420475cf 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-06-09 +date: 2023-06-12 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 73c1cb092baca..ab8ed09aec946 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-06-09 +date: 2023-06-12 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 efe149ba4ccd3..c2297b486589e 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-06-09 +date: 2023-06-12 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 ed2aa401a0d03..15762da981e16 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-06-09 +date: 2023-06-12 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 aaa2359ce6c42..e63041bbad898 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-06-09 +date: 2023-06-12 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 e2bcce2ff5d33..1a39a8ed9802f 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-06-09 +date: 2023-06-12 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 cba9bdf37e4b6..3a897d3a24dd3 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-06-09 +date: 2023-06-12 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 f0586c8c1a950..196323f6b8578 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-06-09 +date: 2023-06-12 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 3bbb9a98b6a7a..9b47f81c00568 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-06-09 +date: 2023-06-12 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 885172ac966c2..ce3f6d7d07eba 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-06-09 +date: 2023-06-12 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 2b308b34a583d..628ec31204540 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 6c4bba20e94c8..33e38b1d866fd 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-06-09 +date: 2023-06-12 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 38d8a5d3b134f..a7dd8d63ab404 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-06-09 +date: 2023-06-12 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 22801e9a1e00f..fad52a308cd63 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-06-09 +date: 2023-06-12 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 7706d9fcb4ce1..638a294a268d8 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-06-09 +date: 2023-06-12 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 6c659475292b0..3917c4690e560 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-06-09 +date: 2023-06-12 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 0310c575d0de5..53b51d34cabb1 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-06-09 +date: 2023-06-12 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 0fb5c41c1a5e6..157ac0efa1865 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-06-09 +date: 2023-06-12 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 aa2d6a0d84430..20ed8c79a321c 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-06-09 +date: 2023-06-12 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 ac596448d5df7..8f83b49335d3d 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-06-09 +date: 2023-06-12 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 fcec502153aa9..127cceb88d450 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-06-09 +date: 2023-06-12 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 c297935a4b6ab..b26f914349997 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-06-09 +date: 2023-06-12 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 abb8b5bb60471..c4b5f04dd51e6 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-06-09 +date: 2023-06-12 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 081ef22cc7543..ca1c9cff073b1 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-06-09 +date: 2023-06-12 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 7b64afd1d1540..01ac6e407e086 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-06-09 +date: 2023-06-12 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 77e43df3b2623..2154d215a3df4 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-06-09 +date: 2023-06-12 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 d80b7fd9b9812..b6db26cd777e7 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-06-09 +date: 2023-06-12 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 2b2ac3caf08ec..ce3e32258ef00 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-06-09 +date: 2023-06-12 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 18a618a4ab2ad..fd80ded90adce 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-06-09 +date: 2023-06-12 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 c58b18343088d..4ecd8ea1a1d4a 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-06-09 +date: 2023-06-12 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 bf967d2dfab9b..176b4906dcd67 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-06-09 +date: 2023-06-12 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 fe8d95040adc8..52eee93f31979 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-06-09 +date: 2023-06-12 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 37234abb69f41..8132cf3697e3e 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-06-09 +date: 2023-06-12 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 5c8e8a45d8b83..45bcb24045564 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-06-09 +date: 2023-06-12 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 34254b0617de8..1275e227c0a38 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-06-09 +date: 2023-06-12 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 316fc5a4866c1..8b56eac0c9c4e 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-06-09 +date: 2023-06-12 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 ef1722f4f8441..7aca691309274 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-06-09 +date: 2023-06-12 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 7d8fd92af3d10..b71418729d2c6 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-06-09 +date: 2023-06-12 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 2f3d0d112f976..4eb848224af79 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-06-09 +date: 2023-06-12 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 bb268f122222d..1a7e0231d415d 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-06-09 +date: 2023-06-12 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 7b0e62c412b8b..6d6620abbd092 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-06-09 +date: 2023-06-12 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 f0dd7875b9d72..08a3a118a7aa4 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-06-09 +date: 2023-06-12 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 0bd24b7a8705c..73096ae3b2ec1 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-06-09 +date: 2023-06-12 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 2dea7d4b955b1..e084d6aefcf83 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-06-09 +date: 2023-06-12 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 1dee7e6966db1..7376682018fa0 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-06-09 +date: 2023-06-12 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 ce6a357007530..6894ca1ae8473 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-06-09 +date: 2023-06-12 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 77b6ce48df08d..144e14685ca70 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-06-09 +date: 2023-06-12 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 0f87f3b98ec45..6e29b09ea6267 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-06-09 +date: 2023-06-12 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 7a6afe5428299..286d28fa772aa 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-06-09 +date: 2023-06-12 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 62330a53e54ae..71631ce1273d7 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-06-09 +date: 2023-06-12 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 e6af8e983c15d..f150eada608df 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-06-09 +date: 2023-06-12 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 6f6bc33cda45a..751b861f29a4a 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-06-09 +date: 2023-06-12 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 30a893b45abc7..2a00f8d4809b4 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-06-09 +date: 2023-06-12 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 da8f315f66013..dcea9918b8923 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-06-09 +date: 2023-06-12 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 57b3aae4ed9d5..fae2f939ba51f 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-06-09 +date: 2023-06-12 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 55a1b4179fa49..2f6cc8ef67c9c 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-06-09 +date: 2023-06-12 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 d8926ba468001..1337da57e9f1a 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-06-09 +date: 2023-06-12 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 080c2d7bf0786..f0d997b4be704 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-06-09 +date: 2023-06-12 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 7e22536c0c2a9..bcda0da6e8781 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index ce0b1416b884b..c895895ab82d5 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-06-09 +date: 2023-06-12 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 e0543b9d3a32e..2b1ac9ce0e850 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-06-09 +date: 2023-06-12 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 83852a787793a..aa02a4604f12d 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-06-09 +date: 2023-06-12 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 f1da905fd2595..af839965b86f0 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-06-09 +date: 2023-06-12 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 f0d3cdd7d04bc..afc9cf6a64792 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-06-09 +date: 2023-06-12 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 1a60da20a9fdb..6dae33b30f47b 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-06-09 +date: 2023-06-12 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 8c2d50714a2b3..36bc57f52b34e 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-06-09 +date: 2023-06-12 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 c70f5cd31f0a8..2dc81dac1487d 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-06-09 +date: 2023-06-12 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 78b6a6bd4fbfe..7d92bfe5ccc80 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-06-09 +date: 2023-06-12 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 a91a638665a82..77903801cb51f 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-06-09 +date: 2023-06-12 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 0c2a03b6b9737..f0bf5af06c579 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index a622731e8726f..d74633c2b4415 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -589,10 +589,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/plugin.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/dashboard_actions/index.ts" diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 51d623cca4cfb..35b2341636c76 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-06-09 +date: 2023-06-12 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 28c56207f0172..30d63a1019adf 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-06-09 +date: 2023-06-12 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 fb3c285f866da..1bb9bd23969bd 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-06-09 +date: 2023-06-12 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 b10c38d556699..352bdbca91bff 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-06-09 +date: 2023-06-12 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 53a44a7bbba89..56229de586f0f 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-06-09 +date: 2023-06-12 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 5739c96fdaa33..2570602c2d783 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-06-09 +date: 2023-06-12 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 431a2a38f419d..a22d28db8791b 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-06-09 +date: 2023-06-12 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 106da1c0928e7..b50f62800db9f 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-06-09 +date: 2023-06-12 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 1181772b60cf4..7772a08e56a76 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-06-09 +date: 2023-06-12 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 0bb6039102fdf..96c06f264c121 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-06-09 +date: 2023-06-12 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 d799c1c0514e8..98c6ae431c05a 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-06-09 +date: 2023-06-12 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 49f639162f0fc..e355f2d30c751 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-06-09 +date: 2023-06-12 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 84df21e36cd69..503e7691b561a 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-06-09 +date: 2023-06-12 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 5573877b40eba..5034fa1a51046 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-06-09 +date: 2023-06-12 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 38681a41a4962..dde8ad1cecb0a 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-06-09 +date: 2023-06-12 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 b7034d788eac4..56baa7a598915 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-06-09 +date: 2023-06-12 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 de8af03f4c4a8..590da10192643 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-06-09 +date: 2023-06-12 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 cb0f662683e44..edcb5e8fdb5f8 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-06-09 +date: 2023-06-12 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 d6ce3c8ea6df3..5b278e5d5878f 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-06-09 +date: 2023-06-12 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 b7bed2431dc76..b86cbe616e04d 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-06-09 +date: 2023-06-12 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 d455bcaf270df..e39b6a3186cfd 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-06-09 +date: 2023-06-12 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 e102e98229414..10b703f0fdef1 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-06-09 +date: 2023-06-12 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 b9940cd0fb426..24a9e8793b9a7 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-06-09 +date: 2023-06-12 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 f14dfd6c0e3c0..b249de8d0e2f1 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-06-09 +date: 2023-06-12 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 87be28dd62dc3..ac33ee4609b86 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-06-09 +date: 2023-06-12 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 a5ab48d82f76d..9db24b9ec9700 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-06-09 +date: 2023-06-12 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 e13ce7c6c0310..9670d0711e1aa 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-06-09 +date: 2023-06-12 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 8f63ee3585a85..1f372c4fe08e9 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-06-09 +date: 2023-06-12 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 e1a33d0ba6e4e..1ce98adafad73 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-06-09 +date: 2023-06-12 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 1eb9fe384900b..e24b6cff1c760 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-06-09 +date: 2023-06-12 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 1ca982ae11385..f1276af829aab 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-06-09 +date: 2023-06-12 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 7551856d57e36..a83527f84194f 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-06-09 +date: 2023-06-12 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 177e8a86af0f9..3359a493a54f6 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-06-09 +date: 2023-06-12 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 1af716786f98f..01699b23987d3 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 64def329cfc86..ed3fff499e972 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -105,14 +105,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/types.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/types.ts" @@ -978,62 +970,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/plugin.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/types.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/types.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/plugin.tsx" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/plugin.tsx" - }, { "plugin": "eventAnnotation", "path": "src/plugins/event_annotation/public/event_annotation_service/service.tsx" @@ -1190,10 +1126,6 @@ "plugin": "savedObjects", "path": "src/plugins/saved_objects/public/saved_object/helpers/save_with_confirmation.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx" @@ -1526,10 +1458,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx" - }, { "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts" @@ -1775,18 +1703,6 @@ "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/dashboards/dashboards_service.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx" @@ -2094,10 +2010,6 @@ "plugin": "@kbn/core-saved-objects-browser-mocks", "path": "packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, { "plugin": "savedObjects", "path": "src/plugins/saved_objects/public/saved_object/saved_object.test.ts" @@ -2172,10 +2084,6 @@ "plugin": "dataViews", "path": "src/plugins/data_views/public/saved_objects_client_wrapper.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, { "plugin": "discover", "path": "src/plugins/discover/public/application/main/services/discover_state.test.ts" @@ -3235,22 +3143,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/application/utils/search_utils.ts" diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 2798e849d00b0..262b1fafd175b 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-06-09 +date: 2023-06-12 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.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index d203f470ce7ba..3d06b41356560 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -2830,42 +2830,6 @@ "plugin": "taskManager", "path": "x-pack/plugins/task_manager/server/task_store.test.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 3184ffe668957..54aeb4e2032c6 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-06-09 +date: 2023-06-12 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 c294cf9e57b53..fd234850ce75a 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-06-09 +date: 2023-06-12 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 011e916299768..319dcf892c6d3 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-06-09 +date: 2023-06-12 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 885e8924ef303..8da1b989040d5 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-06-09 +date: 2023-06-12 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 51910a3b25902..0f07739c6a2a8 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-06-09 +date: 2023-06-12 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 d06d0f7a37754..8be5068cb0861 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-06-09 +date: 2023-06-12 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.devdocs.json b/api_docs/kbn_core_saved_objects_browser_mocks.devdocs.json index 0213de518fa3d..0a77efa36451b 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.devdocs.json +++ b/api_docs/kbn_core_saved_objects_browser_mocks.devdocs.json @@ -56,14 +56,6 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/dashboard_service.test.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts" - }, { "plugin": "dataViews", "path": "src/plugins/data_views/public/saved_objects_client_wrapper.test.ts" diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index d4ee3aaeb5ee3..eade4da3db01b 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-06-09 +date: 2023-06-12 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 bc0784c865d3f..49d4c60580fc4 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -1787,14 +1787,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/shareable_runtime/types.ts" @@ -1879,38 +1871,6 @@ "plugin": "@kbn/core", "path": "src/core/types/index.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, { "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" @@ -1918,14 +1878,6 @@ { "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" } ], "initialIsOpen": false @@ -2594,30 +2546,6 @@ "plugin": "controls", "path": "src/plugins/controls/common/control_group/control_group_persistable_state.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts" - }, { "plugin": "savedObjectsTagging", "path": "x-pack/plugins/saved_objects_tagging/common/references.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 6668217563210..fc568b600be44 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-06-09 +date: 2023-06-12 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 9e656d2a40b38..a45520b51a11a 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-06-09 +date: 2023-06-12 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 d7b1bdf906075..1f99af59c449e 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-06-09 +date: 2023-06-12 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 92c96bdbd76b7..7a1e79ca9b7e5 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-06-09 +date: 2023-06-12 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 280ac3c3a08ad..38f6316c0d81c 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index 0311c3ea8f4ef..82abbb4057839 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -6295,42 +6295,6 @@ "plugin": "taskManager", "path": "x-pack/plugins/task_manager/server/task_store.test.ts" }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/dashboard_telemetry.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/server/usage/find_by_value_embeddables.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 8e979b15525b4..7e76503f19e08 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-06-09 +date: 2023-06-12 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 c4a14786a1365..58fbdb1dc0cce 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-06-09 +date: 2023-06-12 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 7a112e2070885..1e3de783640c3 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-06-09 +date: 2023-06-12 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 7cb50a1adc70b..c72dd3022ca73 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-06-09 +date: 2023-06-12 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 22135c52bca00..0fd693f298a75 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-06-09 +date: 2023-06-12 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 c1a132e312f75..6caae9a1faa67 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-06-09 +date: 2023-06-12 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 9fa69db9ce25d..5f806eda7c4a1 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-06-09 +date: 2023-06-12 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 d80baa987290c..a71d675271f00 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-06-09 +date: 2023-06-12 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 1967420277030..33f07a6fd0121 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-06-09 +date: 2023-06-12 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 7e08d85699e8f..1b3e420582ab7 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-06-09 +date: 2023-06-12 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 79a51bceacc20..014c2030811bf 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-06-09 +date: 2023-06-12 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 3497fea004373..9fcbe55daf7e9 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-06-09 +date: 2023-06-12 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 a18bdeb47ff46..c288ef3f53dc6 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-06-09 +date: 2023-06-12 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 86685bf54f2cd..fdd747daf6f8e 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-06-09 +date: 2023-06-12 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 f486f4f21c93d..431818373db21 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-06-09 +date: 2023-06-12 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 82908127a6068..72abd2482fcd3 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-06-09 +date: 2023-06-12 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 b9e51e5a0b937..b8ab91e01d5d1 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-06-09 +date: 2023-06-12 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 bfe518acc1687..c408c2b590488 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-06-09 +date: 2023-06-12 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 50c90aee47209..0dfb5d514c220 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-06-09 +date: 2023-06-12 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 4adc23e3ed341..d7615e3cc7a78 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-06-09 +date: 2023-06-12 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 e87de19d50ccc..fa45cb639b96d 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-06-09 +date: 2023-06-12 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 67c77d84b522e..b9664fce1d9cc 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-06-09 +date: 2023-06-12 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 5db0b703d40ac..7e05896b400c7 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-06-09 +date: 2023-06-12 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 c60bc565b3f3a..374479d7a8d0e 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-06-09 +date: 2023-06-12 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 724f089a533c5..3423bc6b3fe45 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-06-09 +date: 2023-06-12 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 ead30d42e8b14..816e36eaf966d 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-06-09 +date: 2023-06-12 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 f4a91f4f2ee48..d02845b5d0ea7 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-06-09 +date: 2023-06-12 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 6e137fefecaf6..d25ec895cac20 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-06-09 +date: 2023-06-12 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 199ab719a9612..b8a739ffa6e90 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-06-09 +date: 2023-06-12 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 766e636238217..2c7ff9315a5b4 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-06-09 +date: 2023-06-12 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 e3208d7149669..408e419ac8004 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-06-09 +date: 2023-06-12 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 33634f6ffe776..0a89f2b2dcaf3 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-06-09 +date: 2023-06-12 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 143a483503bcc..2f8e5194b3710 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 3bccb43953ff0..d02b45d6f2d1c 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index addd7e2dce33e..e749fd778a994 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-06-09 +date: 2023-06-12 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 d62e2092f5a8f..bdb8916cca9cf 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-06-09 +date: 2023-06-12 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 954236ee944ba..e1e235b8beb33 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-06-09 +date: 2023-06-12 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 f171a8b9be5bb..422290eb0a04c 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 9d09820f264e5..99d817bb29acf 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-06-09 +date: 2023-06-12 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 f83cb8c35decb..31cf3d16e2711 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-06-09 +date: 2023-06-12 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 c095b34d58ed6..8649c439e648f 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-06-09 +date: 2023-06-12 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 b087db512ab36..587f6d834f23f 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-06-09 +date: 2023-06-12 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 2033b8aebf06e..b23a13692542d 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-06-09 +date: 2023-06-12 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 d65bda36daa47..f8f7a7e084f40 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-06-09 +date: 2023-06-12 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 b0bea525ee053..cbfdb52872b8b 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-06-09 +date: 2023-06-12 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 6d15802be7ee3..ad704adc266f5 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-06-09 +date: 2023-06-12 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 17e9511a2d626..7cd74c0a8aad4 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-06-09 +date: 2023-06-12 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 abc5619af9a15..054523d9e05d7 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-06-09 +date: 2023-06-12 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 c06970cb7f0ea..0f57c58f6eb23 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-06-09 +date: 2023-06-12 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 2a610e6183c0e..98133ca4e327a 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-06-09 +date: 2023-06-12 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 085f075770b3a..9a1aa4edca3e8 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-06-09 +date: 2023-06-12 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 bdc39aa096348..14e9e5a21cf91 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-06-09 +date: 2023-06-12 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 8b28f30935cb7..b07d2b9983c26 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-06-09 +date: 2023-06-12 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 70287b0c0a628..122f08264e850 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-06-09 +date: 2023-06-12 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 b3cbaec2cdfec..6f11e7a33f361 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-06-09 +date: 2023-06-12 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 b4d2ec8f63d69..631a01e530343 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index f9bda0e55a670..bb6f91baa7fd4 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-06-09 +date: 2023-06-12 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 8ba7c60f100b8..520519edbacc0 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-06-09 +date: 2023-06-12 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 92506d56dd84f..42042749244b1 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-06-09 +date: 2023-06-12 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 06cf33f799d0e..97da068faeb8c 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-06-09 +date: 2023-06-12 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 ddc83cbe17903..ef717d7c21d25 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-06-09 +date: 2023-06-12 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 1db9b74883060..d2ba7d83c63d9 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-06-09 +date: 2023-06-12 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 7ae5692797de2..c069b4b85445e 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-06-09 +date: 2023-06-12 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 26e33cdea399b..2f06ffe652ea9 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-06-09 +date: 2023-06-12 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 7aee6853b1ecb..880f2b5610a1a 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-06-09 +date: 2023-06-12 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 4c91e4e38b192..f04fe71128479 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-06-09 +date: 2023-06-12 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 f697d058bf1b5..81dfe3d15d1d0 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-06-09 +date: 2023-06-12 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 8b0af0e833819..294ad43131ea2 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-06-09 +date: 2023-06-12 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 d694ce0e1e36d..3e18185e371cb 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-06-09 +date: 2023-06-12 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 b6a492107ddc1..b2186f544973f 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-06-09 +date: 2023-06-12 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 a18f1058b59ff..b51094ff2e508 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-06-09 +date: 2023-06-12 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 f2f817342ca86..58f6cba416ce7 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-06-09 +date: 2023-06-12 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 993433808aca8..de53a581f76be 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-06-09 +date: 2023-06-12 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 3ee00d7679fc0..1c238e32a0763 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-06-09 +date: 2023-06-12 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 6289e1672f972..3488a4f8b9137 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-06-09 +date: 2023-06-12 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 89785ace1974e..9c1d28acc625f 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-06-09 +date: 2023-06-12 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 6963d2ec981a5..e2cea73da5861 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-06-09 +date: 2023-06-12 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 dc80d024a5995..673d291bb6cae 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 0cd40caea9903..bfcbadbcb6a6e 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-06-09 +date: 2023-06-12 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 71ef39ba8ded5..668afee744700 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-06-09 +date: 2023-06-12 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 952af5dbeb452..1176da6121f8d 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-06-09 +date: 2023-06-12 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 95d44423f5b52..1142959b6437e 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-06-09 +date: 2023-06-12 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 3060ec54e1950..b0d554ababa47 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-06-09 +date: 2023-06-12 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 4fcfa6ec202e0..5dab4c91cddd6 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-06-09 +date: 2023-06-12 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 554e835990359..d8b2eee88598c 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-06-09 +date: 2023-06-12 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 7871f8de0018d..1527a3d94d56b 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-06-09 +date: 2023-06-12 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 2b4f3c3bdf8e3..4d2ebbc848a6d 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-06-09 +date: 2023-06-12 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 08fafe677bdc8..8d096f4506a5e 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-06-09 +date: 2023-06-12 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 33ca1de5bf5b2..316668a62d012 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-06-09 +date: 2023-06-12 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 c2331c1b5ce75..8efdecc7bc330 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-06-09 +date: 2023-06-12 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 eae968193a16b..74e4bd426f667 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-06-09 +date: 2023-06-12 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 17f942f75aecd..0a736fb8e23a4 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-06-09 +date: 2023-06-12 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 43520831faa54..242e57cc59c46 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-06-09 +date: 2023-06-12 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 daecf388436c1..8c76dfc31ad5f 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-06-09 +date: 2023-06-12 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 3243936fcfc63..a2a5f1b86a1c6 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-06-09 +date: 2023-06-12 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 790afd82bab5a..55f2d087cb368 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-06-09 +date: 2023-06-12 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 3686f0f94c984..e459b43aad539 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-06-09 +date: 2023-06-12 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 30c4a0b7a72ca..32bb5cd28872d 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-06-09 +date: 2023-06-12 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 2d8cb76cd5550..665f2360f4bb7 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-06-09 +date: 2023-06-12 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 eb1f69d030485..7243f6841ac0f 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-06-09 +date: 2023-06-12 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 0363b018811f5..c8db4da80c981 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-06-09 +date: 2023-06-12 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 630cf144bac5c..57fb0d24d707f 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-06-09 +date: 2023-06-12 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 d3a4f594df337..4f3c7a395d7f6 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-06-09 +date: 2023-06-12 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 d6d1545775693..4c18aced45b88 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-06-09 +date: 2023-06-12 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 ef75a636e16a2..26cd05cf0bea8 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-06-09 +date: 2023-06-12 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 e2b6b153dc1ef..5574ba6eb51d6 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-06-09 +date: 2023-06-12 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 c5d34dbe770d0..9fd3d3979488e 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-06-09 +date: 2023-06-12 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 15ed8584b43a6..a1eef1d51fb3d 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-06-09 +date: 2023-06-12 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 90c45cef989c8..72d00c2aecbf3 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-06-09 +date: 2023-06-12 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 1b7abff29c028..d262263e40af7 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-06-09 +date: 2023-06-12 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 6982f10665bfd..79903e66e2acd 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-06-09 +date: 2023-06-12 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 7a45c515f2e8d..2df7c755176f2 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-06-09 +date: 2023-06-12 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 d1919aeb6a550..59227d418d529 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-06-09 +date: 2023-06-12 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 7b65c56708e88..b01e538fa9069 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-06-09 +date: 2023-06-12 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 9b0d822b1196c..ddaa4cf97a63c 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-06-09 +date: 2023-06-12 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 1f6b542a33667..70a4fa89c7613 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-06-09 +date: 2023-06-12 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 e0ad59856068d..d5f16962b62a5 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 87d147587b1b5..4cbe62a5458fc 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-06-09 +date: 2023-06-12 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 69a9d9dc41b73..5a86fcfaccd0a 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-06-09 +date: 2023-06-12 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 154b5c2c1e981..bf99b0164df0a 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-06-09 +date: 2023-06-12 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 278971fa36d8e..4d1a233f09846 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-06-09 +date: 2023-06-12 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 cc3b5a87de8f9..39648c191bdf9 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-06-09 +date: 2023-06-12 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 525af5119e290..295993788d10d 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-06-09 +date: 2023-06-12 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 7c26bf5a61e01..746153cc5a141 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-06-09 +date: 2023-06-12 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 333a551b50b75..e42afa61b610c 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-06-09 +date: 2023-06-12 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 2cb766ce0301f..3850d7f8579c6 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-06-09 +date: 2023-06-12 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 51b4af90b1822..59987588339f8 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-06-09 +date: 2023-06-12 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 d2a6af7619629..7778123171567 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-06-09 +date: 2023-06-12 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 3d2144e2431ee..0c7dbec03a716 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-06-09 +date: 2023-06-12 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 c5468737ae078..033a0fc1fa23e 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-06-09 +date: 2023-06-12 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 db19c88162894..dd6084e9bf70c 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-06-09 +date: 2023-06-12 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 e4264d973e1eb..bb7f6d20d3f03 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-06-09 +date: 2023-06-12 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 0ff4c45f429e8..0488f855d6d83 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-06-09 +date: 2023-06-12 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.devdocs.json b/api_docs/kbn_securitysolution_list_constants.devdocs.json index 62cb9d420c145..aff90a42119f0 100644 --- a/api_docs/kbn_securitysolution_list_constants.devdocs.json +++ b/api_docs/kbn_securitysolution_list_constants.devdocs.json @@ -547,14 +547,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts" - }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/services/exception_lists/get_exception_list_summary.test.ts" diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index d7883c6d0cf6c..246aaeefb87e5 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-06-09 +date: 2023-06-12 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 902591bfb0feb..58defa3b54318 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-06-09 +date: 2023-06-12 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 3acbe23e38f63..8cd5aeaf8a363 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-06-09 +date: 2023-06-12 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 762d89245bfa6..55c8790b5f233 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-06-09 +date: 2023-06-12 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 72b06ad6271bb..58287f518ed60 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-06-09 +date: 2023-06-12 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 8451043268163..9567a3c64624c 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-06-09 +date: 2023-06-12 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 f193509acbef0..e0a39808da43e 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-06-09 +date: 2023-06-12 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 72f3646744a94..3b1ba297fe791 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-06-09 +date: 2023-06-12 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 ca3792493fa8b..280d88dc72791 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-06-09 +date: 2023-06-12 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 0b5c5ba9736ab..a349b00c5d064 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-06-09 +date: 2023-06-12 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 f1b40361d938a..d71b81c50a8db 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-06-09 +date: 2023-06-12 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 76e601cf64711..661e3f3a2484f 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-06-09 +date: 2023-06-12 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 5150e47ef8a05..b61e5f08d0cd8 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-06-09 +date: 2023-06-12 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 dd68043d30f3f..b0c222cd9f7af 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-06-09 +date: 2023-06-12 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 4e0c9de76a6a0..f14ac90a07477 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-06-09 +date: 2023-06-12 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 b5bd2092baa9e..0ff22ce5b016d 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-06-09 +date: 2023-06-12 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 25211030c6a9f..3985e2cc89849 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-06-09 +date: 2023-06-12 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 e9baf2acfc4d6..3a492d5c7391b 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-06-09 +date: 2023-06-12 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 ef96c0b116047..c871030d9de85 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-06-09 +date: 2023-06-12 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 899848aa7387b..45e35e375682e 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-06-09 +date: 2023-06-12 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 3db8fd6ea9123..39221c7248409 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-06-09 +date: 2023-06-12 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 2b3254e3e062c..6e294210f2bb2 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-06-09 +date: 2023-06-12 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 2dfa352b1faed..4eed62acaeae6 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-06-09 +date: 2023-06-12 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 8d47d271c7762..d98202f676d84 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-06-09 +date: 2023-06-12 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 44596d60c1253..1df8f4c1517b3 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-06-09 +date: 2023-06-12 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 5bf76665e9aff..26024ea59f527 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-06-09 +date: 2023-06-12 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 5254eb938bc8c..9a0201028dc2d 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-06-09 +date: 2023-06-12 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 e82e2b7278a9b..b4b943540b3c5 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-06-09 +date: 2023-06-12 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 0208affee5f85..f20254c076bee 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-06-09 +date: 2023-06-12 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 0bd4537c5b9ee..0e72e89ba2a00 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-06-09 +date: 2023-06-12 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 07ff066728771..bf568ecc88306 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-06-09 +date: 2023-06-12 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 ac5f7dd11a782..dd4bf1b03a228 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-06-09 +date: 2023-06-12 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 9f232aff971e3..e1d2dcfe731eb 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-06-09 +date: 2023-06-12 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 37303f9c9fc9b..10ce50aa3ce81 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-06-09 +date: 2023-06-12 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 cd205ff4b0821..c4243d5f060c2 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-06-09 +date: 2023-06-12 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 5697d2e914b29..ef984ef170434 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-06-09 +date: 2023-06-12 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 be164b6e74fb4..994b66158bd76 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-06-09 +date: 2023-06-12 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 c5dc869df8cd2..867d4e9f0f247 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-06-09 +date: 2023-06-12 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 216128637bfff..ed7a954e681fb 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-06-09 +date: 2023-06-12 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 fc47353808bfd..daf8dab66794b 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-06-09 +date: 2023-06-12 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 79a129eed4a0b..f50e95131bb94 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-06-09 +date: 2023-06-12 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 1fce4bb8584fb..e4486399c7110 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-06-09 +date: 2023-06-12 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 561b07f95d765..8252a8deaa23f 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-06-09 +date: 2023-06-12 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 a4431de040208..2ff2efef791fe 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-06-09 +date: 2023-06-12 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 27176445893ad..a17b96d681398 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-06-09 +date: 2023-06-12 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 1d160517ccbe6..520a8bc47f857 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-06-09 +date: 2023-06-12 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 1ac613c8a6f9c..dca4a6f3bacbc 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-06-09 +date: 2023-06-12 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 9a1339ddea152..b1ec70ce1ec02 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-06-09 +date: 2023-06-12 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 6186445d0a195..cc83738ae8cdd 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-06-09 +date: 2023-06-12 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 25c4114d49f1b..fc10c41121f31 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-06-09 +date: 2023-06-12 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 a56ea536737ec..63da31bca65cb 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-06-09 +date: 2023-06-12 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 903c376b4b1ef..653aabea0c7ed 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-06-09 +date: 2023-06-12 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 f9635ee28311e..7ab0bf297aeb9 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-06-09 +date: 2023-06-12 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 a5029c42fc62a..83a0a2d3d696b 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-06-09 +date: 2023-06-12 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 888017635369d..5fdf94cd915ac 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-06-09 +date: 2023-06-12 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 b8eae83c23462..a2bb34afc07e8 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-06-09 +date: 2023-06-12 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 1692da294a393..b3444ebcbe6b9 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-06-09 +date: 2023-06-12 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 72d4fcac53e9e..4575bb5844244 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-06-09 +date: 2023-06-12 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 f0cc573d971c9..df84b0448d586 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-06-09 +date: 2023-06-12 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 4b631f9175d46..69f619b45b2bd 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-06-09 +date: 2023-06-12 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 a5607553421a9..bdc6b11ab0232 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-06-09 +date: 2023-06-12 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 0ed5e251ba2ef..57fb06b449d9e 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-06-09 +date: 2023-06-12 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 cc5f8627a148b..c5df0dd83ca60 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-06-09 +date: 2023-06-12 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 db7ff0a6201a0..bb27bf9c1bbeb 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-06-09 +date: 2023-06-12 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 447c91bcecf3f..bff9f07ba2593 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-06-09 +date: 2023-06-12 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 486225979fb7e..7d561f4bb0bb9 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index fe4b661345e18..7abc8eb86f7cc 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-06-09 +date: 2023-06-12 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 6c9418f4faa95..f7b26df468d49 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-06-09 +date: 2023-06-12 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 e6866c765074b..2ae97b4b30b08 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-06-09 +date: 2023-06-12 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 722a209f5c60d..40bd900cf0767 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-06-09 +date: 2023-06-12 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 8abab68a7ea12..105d3d3d036da 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-06-09 +date: 2023-06-12 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 c41ddeab48abe..294bf059c6e31 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-06-09 +date: 2023-06-12 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 6fdafc2270a3c..22d6db40923d9 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-06-09 +date: 2023-06-12 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 211106c3935f0..58e69f444995a 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-06-09 +date: 2023-06-12 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 c1af32a130cd7..e6a012285f2be 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-06-09 +date: 2023-06-12 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 38550c066b3cc..de49a158fccb5 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-06-09 +date: 2023-06-12 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 9401e6f6cdf3a..df650a8a6c33d 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -2162,6 +2162,20 @@ "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "lens", + "id": "def-public.FormBasedLayer.ignoreGlobalFilters", + "type": "CompoundType", + "tags": [], + "label": "ignoreGlobalFilters", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -10239,7 +10253,7 @@ "section": "def-public.GenericIndexPatternColumn", "text": "GenericIndexPatternColumn" }, - ">; columnOrder: string[]; linkToLayers?: string[] | undefined; incompleteColumns?: Record; ignoreGlobalFilters?: boolean | undefined; columnOrder: string[]; linkToLayers?: string[] | undefined; incompleteColumns?: Record object | null" ], "path": "x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx", @@ -3073,7 +3079,13 @@ "label": "dataFilters", "description": [], "signature": [ - "DataFilters" + { + "pluginId": "maps", + "scope": "common", + "docId": "kibMapsPluginApi", + "section": "def-common.DataFilters", + "text": "DataFilters" + } ], "path": "x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx", "deprecated": false, @@ -4520,6 +4532,83 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "maps", + "id": "def-common.DataFilters", + "type": "Type", + "tags": [], + "label": "DataFilters", + "description": [], + "signature": [ + "{ buffer?: ", + "MapExtent", + " | undefined; extent?: ", + "MapExtent", + " | undefined; filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; query?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | undefined; embeddableSearchContext?: { query?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | undefined; filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; } | undefined; searchSessionId?: string | undefined; timeFilters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + "; timeslice?: ", + "Timeslice", + " | undefined; zoom: number; isReadOnly: boolean; joinKeyFilter?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | undefined; executionContext: ", + { + "pluginId": "@kbn/core-execution-context-common", + "scope": "common", + "docId": "kibKbnCoreExecutionContextCommonPluginApi", + "section": "def-common.KibanaExecutionContext", + "text": "KibanaExecutionContext" + }, + "; }" + ], + "path": "x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "maps", "id": "def-common.DECIMAL_DEGREES_PRECISION", @@ -4807,7 +4896,13 @@ "label": "VectorSourceRequestMeta", "description": [], "signature": [ - "DataFilters", + { + "pluginId": "maps", + "scope": "common", + "docId": "kibMapsPluginApi", + "section": "def-common.DataFilters", + "text": "DataFilters" + }, " & { applyGlobalQuery: boolean; applyGlobalTime: boolean; applyForceRefresh: boolean; sourceQuery?: ", { "pluginId": "@kbn/es-query", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index d541d557f4e4c..ba6b4e24c7dc3 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 266 | 0 | 265 | 29 | +| 267 | 0 | 266 | 28 | ## Client diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 61a495b9313b1..4bb9122037e33 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-06-09 +date: 2023-06-12 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 f7378ee5eac20..5f15fee5dcfb3 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-06-09 +date: 2023-06-12 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 3a2c33b97f1b7..f01984fde4185 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-06-09 +date: 2023-06-12 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 0941c7545911c..e740dbdf7d160 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-06-09 +date: 2023-06-12 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 8cd22c741fa2e..bd4385f77f796 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-06-09 +date: 2023-06-12 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 900a981a5eab0..44287b41e1c40 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-06-09 +date: 2023-06-12 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 e3f37a91b3a35..a4827dda58f7e 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 9b19f32d060c8..e4b092c8c8ed9 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -8884,7 +8884,7 @@ "label": "ObservabilityConfig", "description": [], "signature": [ - "{ readonly coPilot?: Readonly<{} & { enabled: boolean; provider: Readonly<{} & { openAI: Readonly<{} & { apiKey: string; model: string; }>; }> | Readonly<{} & { azureOpenAI: Readonly<{} & { apiKey: string; resourceName: string; deploymentId: string; }>; }>; }> | undefined; readonly enabled: boolean; readonly annotations: Readonly<{} & { enabled: boolean; index: string; }>; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; }>; }" + "{ readonly coPilot?: Readonly<{} & { enabled: boolean; provider: Readonly<{} & { openAI: Readonly<{} & { apiKey: string; model: string; }>; }> | Readonly<{} & { azureOpenAI: Readonly<{} & { apiKey: string; resourceName: string; deploymentId: string; }>; }>; }> | undefined; readonly enabled: boolean; readonly annotations: Readonly<{} & { enabled: boolean; index: string; }>; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; }>; readonly thresholdRule: Readonly<{} & { groupByPageSize: number; }>; }" ], "path": "x-pack/plugins/observability/server/index.ts", "deprecated": false, @@ -10213,6 +10213,86 @@ } ], "objects": [ + { + "parentPluginId": "observability", + "id": "def-server.metricsExplorerViewSavedObjectAttributesRT", + "type": "Object", + "tags": [], + "label": "metricsExplorerViewSavedObjectAttributesRT", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "ExactC", + "<", + "TypeC", + "<{ name: ", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">; }>>, ", + "UnknownRecordC", + "]>" + ], + "path": "x-pack/plugins/observability/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "observability", + "id": "def-server.metricsExplorerViewSavedObjectRT", + "type": "Object", + "tags": [], + "label": "metricsExplorerViewSavedObjectRT", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "TypeC", + "<{ id: ", + "StringC", + "; attributes: ", + "IntersectionC", + "<[", + "ExactC", + "<", + "TypeC", + "<{ name: ", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">; }>>, ", + "UnknownRecordC", + "]>; }>, ", + "PartialC", + "<{ version: ", + "StringC", + "; updated_at: ", + "Type", + "; }>]>" + ], + "path": "x-pack/plugins/observability/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-server.uiSettings", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index c2cda4a347348..e91d4edc85021 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 513 | 43 | 505 | 18 | +| 515 | 43 | 507 | 18 | ## Client diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index fc3a502898d4b..245fbdd2ff152 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-06-09 +date: 2023-06-12 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 e2f8b36481b33..31d31aeb31280 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-06-09 +date: 2023-06-12 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 12912f6b20a8f..7d9235c8863a9 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-06-09 +date: 2023-06-12 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 e3689110f75e4..bf44f63599fd5 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 70553 | 531 | 60413 | 1380 | +| 70509 | 531 | 60373 | 1378 | ## Plugin Directory @@ -36,7 +36,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 91 | 1 | 75 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 101 | 0 | 84 | 30 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 79 | 0 | 64 | 26 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 271 | 16 | 256 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 41 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 1 | 0 | 0 | 0 | @@ -53,7 +53,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | crossClusterReplication | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | customBranding | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Enables customization of Kibana | 0 | 0 | 0 | 0 | | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 274 | 0 | 255 | 1 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 130 | 0 | 125 | 7 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 99 | 0 | 97 | 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. | 3302 | 119 | 2578 | 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 | @@ -65,7 +65,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 83 | 0 | 64 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 546 | 11 | 442 | 4 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 548 | 11 | 444 | 4 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -93,7 +93,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 239 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1182 | 3 | 1066 | 33 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1184 | 3 | 1068 | 33 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -115,14 +115,14 @@ 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. | 618 | 0 | 523 | 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. | 619 | 0 | 524 | 58 | | | [@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-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 210 | 0 | 94 | 51 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 43 | 0 | 43 | 6 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 266 | 0 | 265 | 29 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 267 | 0 | 266 | 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 | 33 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 15 | 3 | 13 | 1 | @@ -130,7 +130,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 0 | 34 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 513 | 43 | 505 | 18 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 515 | 43 | 507 | 18 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 7 | 0 | 7 | 0 | | | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 266 | 1 | 265 | 11 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 7 | @@ -153,7 +153,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) | - | 154 | 2 | 110 | 29 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 155 | 2 | 111 | 29 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 14 | 0 | 13 | 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 | @@ -172,14 +172,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 0 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 17 | 0 | 17 | 0 | | | [@elastic/protections-experience](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 30 | 0 | 14 | 5 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 259 | 1 | 216 | 21 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 257 | 1 | 213 | 22 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 544 | 11 | 518 | 49 | | | [@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) | Contains functionality for the field list which can be integrated into apps | 296 | 0 | 270 | 8 | -| | [@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 | 24 | 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 | | urlDrilldown | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | @@ -200,7 +200,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 1 | 0 | 1 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 52 | 0 | 50 | 5 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 122 | 0 | 119 | 4 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 810 | 12 | 780 | 18 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 811 | 12 | 781 | 18 | | watcher | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | ## Package Directory diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 2d4d43d87b9bf..0c1fe46639bab 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-06-09 +date: 2023-06-12 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 1781d78989a8c..6399530219ba1 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-06-09 +date: 2023-06-12 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 3fa8756e5f2e3..37acb0e166ac3 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-06-09 +date: 2023-06-12 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 56a88f860d1d5..933ce4551e7c9 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-06-09 +date: 2023-06-12 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 329661bd47082..559148443f2b4 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-06-09 +date: 2023-06-12 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 9d732ad9c5928..191c2a5d7bd44 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-06-09 +date: 2023-06-12 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 b3928b43afda4..05fb3fa9d0fc4 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-06-09 +date: 2023-06-12 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 f654acea25c2d..26b4460598768 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-06-09 +date: 2023-06-12 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 afbc887d61d13..35cdc955d4430 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-06-09 +date: 2023-06-12 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 05438d131628c..65f26a65cb4e8 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-06-09 +date: 2023-06-12 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 aadc35fdb96fe..b1553d6ef05b1 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-06-09 +date: 2023-06-12 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 0bd8a3dce59b1..6e6b4315d77da 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-06-09 +date: 2023-06-12 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 76cd163037f62..7b9a228c8e3cb 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-06-09 +date: 2023-06-12 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 42490746a5319..cf2754ce3cab4 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-06-09 +date: 2023-06-12 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 07ed25724b5b8..0d63315a64091 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-06-09 +date: 2023-06-12 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 8f54472f1472b..9b91fec6ea041 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-06-09 +date: 2023-06-12 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 7dd9dd9955e5a..356ec2c55b45a 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-06-09 +date: 2023-06-12 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 99bfe5a22f25a..ed71fa18219a2 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -2223,6 +2223,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "securitySolution", + "id": "def-server.SecuritySolutionApiRequestHandlerContext.getServerBasePath", + "type": "Function", + "tags": [], + "label": "getServerBasePath", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/security_solution/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "securitySolution", "id": "def-server.SecuritySolutionApiRequestHandlerContext.getEndpointAuthz", @@ -2731,7 +2747,7 @@ "signature": [ "readonly (", "AppFeatureSecurityKey", - ".advancedInsights | ", + " | ", "AppFeatureCasesKey", ".casesConnectors)[]" ], diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 14a5c3de46ce0..b61028470c765 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-06-09 +date: 2023-06-12 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 | |-------------------|-----------|------------------------|-----------------| -| 154 | 2 | 110 | 29 | +| 155 | 2 | 111 | 29 | ## Client diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 55cd55804ba83..7a2241c87b87b 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-06-09 +date: 2023-06-12 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 ea7f3e2bda75c..cac7fc492001d 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-06-09 +date: 2023-06-12 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 7b695755d0807..812daf5d9737d 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-06-09 +date: 2023-06-12 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 206c3f94c6115..f4980559298b2 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-06-09 +date: 2023-06-12 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 5ecb3840593af..dd8d3d2d31588 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-06-09 +date: 2023-06-12 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 b751b9d131241..e63bc898b6267 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-06-09 +date: 2023-06-12 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 aad5b07ce6b87..7c57564d39ddb 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-06-09 +date: 2023-06-12 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 ac64c5087d2f1..c821161109ff0 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-06-09 +date: 2023-06-12 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 08b7efce2b1ed..f1f73d87e7cb6 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-06-09 +date: 2023-06-12 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 693bd508ee6e8..ed114100df59b 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-06-09 +date: 2023-06-12 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 f203faa797f9f..3f30a287c945f 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-06-09 +date: 2023-06-12 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 4839b334e0a74..f4aa9e8f623e0 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-06-09 +date: 2023-06-12 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 61a44b30b3a82..d04136bfd842f 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-06-09 +date: 2023-06-12 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 eba4ca899ac54..e799c3ff554fe 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-06-09 +date: 2023-06-12 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 c69b911b643ed..cf6f5983d2fae 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-06-09 +date: 2023-06-12 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 0531f9c27ffb9..eb7b9e7e7afe3 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-06-09 +date: 2023-06-12 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 a826e7db3857d..8943bc7a0bd02 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index c4dec214ca201..90ef42ea48fc8 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -1377,30 +1377,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts" @@ -1437,6 +1413,30 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx" }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" @@ -1942,13 +1942,7 @@ "\nReturns a DataProviderType" ], "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.DataProviderType", - "text": "DataProviderType" - }, + "DataProviderType", " | undefined" ], "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", @@ -1958,6 +1952,196 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer", + "type": "Interface", + "tags": [ + "deprecated" + ], + "label": "DeprecatedRowRenderer", + "description": [ + "\nThis interface should not be used anymore.\nUse the one from `plugins/security_solution/common/types/timeline`." + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx" + } + ], + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.id", + "type": "Enum", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "DeprecatedRowRendererId" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.isInstance", + "type": "Function", + "tags": [], + "label": "isInstance", + "description": [], + "signature": [ + "(data: ", + { + "pluginId": "@kbn/securitysolution-ecs", + "scope": "common", + "docId": "kibKbnSecuritysolutionEcsPluginApi", + "section": "def-common.EcsSecurityExtension", + "text": "EcsSecurityExtension" + }, + ") => boolean" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.isInstance.$1", + "type": "Object", + "tags": [], + "label": "data", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-ecs", + "scope": "common", + "docId": "kibKbnSecuritysolutionEcsPluginApi", + "section": "def-common.EcsSecurityExtension", + "text": "EcsSecurityExtension" + } + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow", + "type": "Function", + "tags": [], + "label": "renderRow", + "description": [], + "signature": [ + "({ contextId, data, isDraggable, scopeId, }: { contextId?: string | undefined; data: ", + { + "pluginId": "@kbn/securitysolution-ecs", + "scope": "common", + "docId": "kibKbnSecuritysolutionEcsPluginApi", + "section": "def-common.EcsSecurityExtension", + "text": "EcsSecurityExtension" + }, + "; isDraggable: boolean; scopeId: string; }) => React.ReactNode" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow.$1", + "type": "Object", + "tags": [], + "label": "{\n contextId,\n data,\n isDraggable,\n scopeId,\n }", + "description": [], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow.$1.contextId", + "type": "string", + "tags": [], + "label": "contextId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow.$1.data", + "type": "Object", + "tags": [], + "label": "data", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-ecs", + "scope": "common", + "docId": "kibKbnSecuritysolutionEcsPluginApi", + "section": "def-common.EcsSecurityExtension", + "text": "EcsSecurityExtension" + } + ], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow.$1.isDraggable", + "type": "boolean", + "tags": [], + "label": "isDraggable", + "description": [], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedRowRenderer.renderRow.$1.scopeId", + "type": "string", + "tags": [], + "label": "scopeId", + "description": [], + "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "timelines", "id": "def-common.EqlOptionsData", @@ -2620,199 +2804,31 @@ }, { "parentPluginId": "timelines", - "id": "def-common.QueryMatch.displayValue", - "type": "CompoundType", - "tags": [], - "label": "displayValue", - "description": [], - "signature": [ - "string | number | boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.QueryMatch.operator", - "type": "CompoundType", - "tags": [], - "label": "operator", - "description": [], - "signature": [ - "\"includes\" | \":\" | \":*\"" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer", - "type": "Interface", - "tags": [], - "label": "RowRenderer", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.id", - "type": "Enum", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "RowRendererId" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.isInstance", - "type": "Function", - "tags": [], - "label": "isInstance", - "description": [], - "signature": [ - "(data: ", - { - "pluginId": "@kbn/securitysolution-ecs", - "scope": "common", - "docId": "kibKbnSecuritysolutionEcsPluginApi", - "section": "def-common.EcsSecurityExtension", - "text": "EcsSecurityExtension" - }, - ") => boolean" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.isInstance.$1", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - { - "pluginId": "@kbn/securitysolution-ecs", - "scope": "common", - "docId": "kibKbnSecuritysolutionEcsPluginApi", - "section": "def-common.EcsSecurityExtension", - "text": "EcsSecurityExtension" - } - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow", - "type": "Function", - "tags": [], - "label": "renderRow", - "description": [], - "signature": [ - "({ contextId, data, isDraggable, scopeId, }: { contextId?: string | undefined; data: ", - { - "pluginId": "@kbn/securitysolution-ecs", - "scope": "common", - "docId": "kibKbnSecuritysolutionEcsPluginApi", - "section": "def-common.EcsSecurityExtension", - "text": "EcsSecurityExtension" - }, - "; isDraggable: boolean; scopeId: string; }) => React.ReactNode" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow.$1", - "type": "Object", - "tags": [], - "label": "{\n contextId,\n data,\n isDraggable,\n scopeId,\n }", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow.$1.contextId", - "type": "string", - "tags": [], - "label": "contextId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow.$1.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - { - "pluginId": "@kbn/securitysolution-ecs", - "scope": "common", - "docId": "kibKbnSecuritysolutionEcsPluginApi", - "section": "def-common.EcsSecurityExtension", - "text": "EcsSecurityExtension" - } - ], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow.$1.isDraggable", - "type": "boolean", - "tags": [], - "label": "isDraggable", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.RowRenderer.renderRow.$1.scopeId", - "type": "string", - "tags": [], - "label": "scopeId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/rows/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } + "id": "def-common.QueryMatch.displayValue", + "type": "CompoundType", + "tags": [], + "label": "displayValue", + "description": [], + "signature": [ + "string | number | boolean | undefined" ], - "returnComment": [] + "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.QueryMatch.operator", + "type": "CompoundType", + "tags": [], + "label": "operator", + "description": [], + "signature": [ + "\"includes\" | \":\" | \":*\"" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4172,18 +4188,6 @@ } ], "enums": [ - { - "parentPluginId": "timelines", - "id": "def-common.DataProviderType", - "type": "Enum", - "tags": [], - "label": "DataProviderType", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.Direction", @@ -4207,18 +4211,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.TimelineType", - "type": "Enum", - "tags": [], - "label": "TimelineType", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "misc": [ @@ -4289,45 +4281,17 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/common/types/header_actions/index.ts" + "path": "x-pack/plugins/security_solution/common/types/timeline/cells/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/common/types/header_actions/index.ts" + "path": "x-pack/plugins/security_solution/common/types/timeline/cells/index.ts" }, { "plugin": "securitySolution", @@ -4335,11 +4299,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/common/types/timeline/cells/index.ts" + "path": "x-pack/plugins/security_solution/common/types/header_actions/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/common/types/timeline/cells/index.ts" + "path": "x-pack/plugins/security_solution/common/types/header_actions/index.ts" }, { "plugin": "securitySolution", @@ -4469,6 +4433,34 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/mock.ts" }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts" @@ -4776,72 +4768,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "timelines", - "id": "def-common.CellValueElementProps", - "type": "Type", - "tags": [], - "label": "CellValueElementProps", - "description": [ - "The following props are provided to the function called by `renderCellValue`" - ], - "signature": [ - "EuiDataGridCellValueElementProps", - " & { asPlainText?: boolean | undefined; browserFields?: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.BrowserFields", - "text": "BrowserFields" - }, - " | undefined; data: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.TimelineNonEcsData", - "text": "TimelineNonEcsData" - }, - "[]; ecsData?: ", - { - "pluginId": "@kbn/securitysolution-ecs", - "scope": "common", - "docId": "kibKbnSecuritysolutionEcsPluginApi", - "section": "def-common.EcsSecurityExtension", - "text": "EcsSecurityExtension" - }, - " | undefined; eventId: string; globalFilters?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined; header: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, - "; isDraggable: boolean; isTimeline?: boolean | undefined; linkValues: string[] | undefined; rowRenderers?: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.RowRenderer", - "text": "RowRenderer" - }, - "[] | undefined; setFlyoutAlert?: ((data: any) => void) | undefined; scopeId: string; truncate?: boolean | undefined; key?: string | undefined; closeCellPopover?: (() => void) | undefined; enableActions?: boolean | undefined; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/cells/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.ColumnHeaderOptions", @@ -4882,13 +4808,7 @@ "description": [], "signature": [ "{ type?: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.DataProviderType", - "text": "DataProviderType" - }, + "DataProviderType", " | undefined; id: string; name: string; enabled: boolean; excluded: boolean; kqlQuery: string; queryMatch: ", { "pluginId": "timelines", @@ -4919,6 +4839,100 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "timelines", + "id": "def-common.DeprecatedCellValueElementProps", + "type": "Type", + "tags": [ + "deprecated" + ], + "label": "DeprecatedCellValueElementProps", + "description": [ + "\nThe following props are provided to the function called by `renderCellValue`.\nWarning: This type might be outdated. Therefore, migrate to the new one from\n`plugins/security_solution/common/types/timeline/cells/index.ts`." + ], + "signature": [ + "EuiDataGridCellValueElementProps", + " & { asPlainText?: boolean | undefined; browserFields?: ", + { + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.BrowserFields", + "text": "BrowserFields" + }, + " | undefined; data: ", + { + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.TimelineNonEcsData", + "text": "TimelineNonEcsData" + }, + "[]; ecsData?: ", + { + "pluginId": "@kbn/securitysolution-ecs", + "scope": "common", + "docId": "kibKbnSecuritysolutionEcsPluginApi", + "section": "def-common.EcsSecurityExtension", + "text": "EcsSecurityExtension" + }, + " | undefined; eventId: string; globalFilters?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | undefined; header: ", + { + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.ColumnHeaderOptions", + "text": "ColumnHeaderOptions" + }, + "; isDraggable: boolean; isTimeline?: boolean | undefined; linkValues: string[] | undefined; rowRenderers?: ", + { + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.DeprecatedRowRenderer", + "text": "DeprecatedRowRenderer" + }, + "[] | undefined; setFlyoutAlert?: ((data: any) => void) | undefined; scopeId: string; truncate?: boolean | undefined; key?: string | undefined; closeCellPopover?: (() => void) | undefined; enableActions?: boolean | undefined; }" + ], + "path": "x-pack/plugins/timelines/common/types/timeline/cells/index.ts", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/alerts_table/render_cell_value.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/alerts_table/render_cell_value.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx" + } + ], + "initialIsOpen": false + }, { "parentPluginId": "timelines", "id": "def-common.EMPTY_INDEX_FIELDS", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 0862e2a264f91..124eede954a8d 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 259 | 1 | 216 | 21 | +| 257 | 1 | 213 | 22 | ## Client diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 7378787d19641..925266b4ebdfd 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-06-09 +date: 2023-06-12 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 59153173ad8d5..75b3a6c3576e9 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-06-09 +date: 2023-06-12 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 c6a43c48ee603..e0ad80da5f72e 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-06-09 +date: 2023-06-12 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 5f1db0437c391..ad33a26d1c5be 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 025eadda0fba5..3692a9c703ea0 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2023-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.devdocs.json b/api_docs/unified_histogram.devdocs.json index ed12834b728a3..566ddc7a5e4c0 100644 --- a/api_docs/unified_histogram.devdocs.json +++ b/api_docs/unified_histogram.devdocs.json @@ -826,6 +826,26 @@ "path": "src/plugins/unified_histogram/public/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "unifiedHistogram", + "id": "def-public.UnifiedHistogramServices.capabilities", + "type": "Object", + "tags": [], + "label": "capabilities", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-capabilities-common", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesCommonPluginApi", + "section": "def-common.Capabilities", + "text": "Capabilities" + } + ], + "path": "src/plugins/unified_histogram/public/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index dfd06274de5e7..dbbc6b91cd908 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-06-09 +date: 2023-06-12 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 | 24 | 2 | ## Client diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 1852f80234dae..e1dda8baa05cc 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-06-09 +date: 2023-06-12 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 9db3cdfb2a0b0..9ac87b5a09b1f 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-06-09 +date: 2023-06-12 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 88cc07b84c434..00ff597922848 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-06-09 +date: 2023-06-12 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 7ba560f255b7f..67a53838109b7 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-06-09 +date: 2023-06-12 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 288010569f5b6..ac4769e50594c 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-06-09 +date: 2023-06-12 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 69d30ce75c276..cebbf29ed246c 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-06-09 +date: 2023-06-12 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 5a9fc000c3649..42aa7f61aac26 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-06-09 +date: 2023-06-12 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 fad67bec1a417..2cee75bb8f802 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-06-09 +date: 2023-06-12 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 b3e456e538a28..b9d565f0a4a89 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-06-09 +date: 2023-06-12 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 ec6a8e7c818d2..93bc7fd588f3b 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-06-09 +date: 2023-06-12 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 1d9064535de10..ef7fc55aadc05 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-06-09 +date: 2023-06-12 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 2432657ddd40b..ad380a2ccc02d 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-06-09 +date: 2023-06-12 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 c1a6121193980..00f882ea61186 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-06-09 +date: 2023-06-12 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 3383da108b9c7..4af2e6abf9440 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-06-09 +date: 2023-06-12 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 12b390d9ed5ed..ccb97f2985bb7 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-06-09 +date: 2023-06-12 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 f0250fc6741a9..ead8c9c851a51 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizationUiComponents'] --- import visualizationUiComponentsObj from './visualization_ui_components.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index 37b6caac4f06c..c87c1a63cca33 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -10166,6 +10166,17 @@ "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Layer.ignoreGlobalFilters", + "type": "boolean", + "tags": [], + "label": "ignoreGlobalFilters", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 3318c3af30d82..cd22865640b97 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-06-09 +date: 2023-06-12 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.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 | |-------------------|-----------|------------------------|-----------------| -| 810 | 12 | 780 | 18 | +| 811 | 12 | 781 | 18 | ## Client diff --git a/config/serverless.security.yml b/config/serverless.security.yml index e9375bd10e2a2..741439b0bc336 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -8,7 +8,11 @@ xpack.uptime.enabled: false ## Enable the Serverless Security plugin xpack.serverless.security.enabled: true -xpack.serverless.security.productTypes: [{ product_line: 'security', product_tier: 'complete' }] +xpack.serverless.security.productTypes: + [ + { product_line: 'security', product_tier: 'complete' }, + { product_line: 'endpoint', product_tier: 'complete' }, + ] ## Set the home route uiSettings.overrides.defaultRoute: /app/security/get_started diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index b1bb977f31f78..f4860d729942c 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -46,8 +46,6 @@ Review important information about the {kib} 8.x releases. [[release-notes-8.8.1]] == {kib} 8.8.1 -coming::[8.8.1] - Review the following information about the {kib} 8.8.1 release. [float] diff --git a/docs/api-generated/rules/rule-apis-passthru.asciidoc b/docs/api-generated/rules/rule-apis-passthru.asciidoc index c4f472c161efe..e621edeca2dd4 100644 --- a/docs/api-generated/rules/rule-apis-passthru.asciidoc +++ b/docs/api-generated/rules/rule-apis-passthru.asciidoc @@ -100,6 +100,20 @@ Any modifications made to this file will be overwritten.
{
   "throttle" : "10m",
   "created_at" : "2022-12-05T23:36:58.284Z",
+  "api_key_created_by_user" : false,
+  "enabled" : true,
+  "running" : true,
+  "notify_when" : "onActiveAlert",
+  "next_run" : "2022-12-06T00:14:43.818Z",
+  "updated_at" : "2022-12-05T23:36:58.284Z",
+  "execution_status" : {
+    "last_execution_date" : "2022-12-06T00:13:43.89Z",
+    "last_duration" : 55,
+    "status" : "ok"
+  },
+  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "consumer" : "alerts",
   "last_run" : {
     "alerts_count" : {
       "ignored" : 6,
@@ -107,39 +121,75 @@ Any modifications made to this file will be overwritten.
       "recovered" : 5,
       "active" : 0
     },
-    "outcome_msg" : "outcome_msg",
+    "outcome_msg" : [ "outcome_msg", "outcome_msg" ],
+    "outcome_order" : 5,
     "warning" : "warning",
     "outcome" : "succeeded"
   },
-  "api_key_created_by_user" : false,
   "params" : {
     "key" : ""
   },
   "created_by" : "elastic",
-  "enabled" : true,
   "muted_alert_ids" : [ "muted_alert_ids", "muted_alert_ids" ],
   "rule_type_id" : "monitoring_alert_cluster_health",
-  "revision" : 5,
+  "revision" : 2,
   "tags" : [ "tags", "tags" ],
-  "running" : true,
   "api_key_owner" : "elastic",
   "schedule" : {
     "interval" : "1m"
   },
-  "notify_when" : "onActiveAlert",
-  "next_run" : "2022-12-06T00:14:43.818Z",
-  "updated_at" : "2022-12-05T23:36:58.284Z",
-  "execution_status" : {
-    "last_execution_date" : "2022-12-06T00:13:43.89Z",
-    "last_duration" : 55,
-    "status" : "ok"
-  },
   "name" : "cluster_health_rule",
   "updated_by" : "elastic",
-  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
-  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
   "mute_all" : false,
   "actions" : [ {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -153,6 +203,54 @@ Any modifications made to this file will be overwritten.
     },
     "group" : "default"
   }, {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -165,8 +263,7 @@ Any modifications made to this file will be overwritten.
       "notify_when" : "onActiveAlert"
     },
     "group" : "default"
-  } ],
-  "consumer" : "alerts"
+  } ]
 }

Produces

@@ -400,6 +497,20 @@ Any modifications made to this file will be overwritten. "data" : [ { "throttle" : "10m", "created_at" : "2022-12-05T23:36:58.284Z", + "api_key_created_by_user" : false, + "enabled" : true, + "running" : true, + "notify_when" : "onActiveAlert", + "next_run" : "2022-12-06T00:14:43.818Z", + "updated_at" : "2022-12-05T23:36:58.284Z", + "execution_status" : { + "last_execution_date" : "2022-12-06T00:13:43.89Z", + "last_duration" : 55, + "status" : "ok" + }, + "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef", + "id" : "b530fed0-74f5-11ed-9801-35303b735aef", + "consumer" : "alerts", "last_run" : { "alerts_count" : { "ignored" : 6, @@ -407,39 +518,75 @@ Any modifications made to this file will be overwritten. "recovered" : 5, "active" : 0 }, - "outcome_msg" : "outcome_msg", + "outcome_msg" : [ "outcome_msg", "outcome_msg" ], + "outcome_order" : 5, "warning" : "warning", "outcome" : "succeeded" }, - "api_key_created_by_user" : false, "params" : { "key" : "" }, "created_by" : "elastic", - "enabled" : true, "muted_alert_ids" : [ "muted_alert_ids", "muted_alert_ids" ], "rule_type_id" : "monitoring_alert_cluster_health", - "revision" : 5, + "revision" : 2, "tags" : [ "tags", "tags" ], - "running" : true, "api_key_owner" : "elastic", "schedule" : { "interval" : "1m" }, - "notify_when" : "onActiveAlert", - "next_run" : "2022-12-06T00:14:43.818Z", - "updated_at" : "2022-12-05T23:36:58.284Z", - "execution_status" : { - "last_execution_date" : "2022-12-06T00:13:43.89Z", - "last_duration" : 55, - "status" : "ok" - }, "name" : "cluster_health_rule", "updated_by" : "elastic", - "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef", - "id" : "b530fed0-74f5-11ed-9801-35303b735aef", "mute_all" : false, "actions" : [ { + "alerts_filter" : { + "timeframe" : { + "hours" : { + "start" : "08:00", + "end" : "17:00" + }, + "timezone" : "Europe/Madrid", + "days" : [ 1, 2, 3, 4, 5 ] + }, + "query" : { + "kql" : "kql", + "filters" : [ { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + }, { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + } ] + } + }, "id" : "9dca3e00-74f5-11ed-9801-35303b735aef", "params" : { "key" : "" @@ -453,6 +600,54 @@ Any modifications made to this file will be overwritten. }, "group" : "default" }, { + "alerts_filter" : { + "timeframe" : { + "hours" : { + "start" : "08:00", + "end" : "17:00" + }, + "timezone" : "Europe/Madrid", + "days" : [ 1, 2, 3, 4, 5 ] + }, + "query" : { + "kql" : "kql", + "filters" : [ { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + }, { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + } ] + } + }, "id" : "9dca3e00-74f5-11ed-9801-35303b735aef", "params" : { "key" : "" @@ -465,11 +660,24 @@ Any modifications made to this file will be overwritten. "notify_when" : "onActiveAlert" }, "group" : "default" - } ], - "consumer" : "alerts" + } ] }, { "throttle" : "10m", "created_at" : "2022-12-05T23:36:58.284Z", + "api_key_created_by_user" : false, + "enabled" : true, + "running" : true, + "notify_when" : "onActiveAlert", + "next_run" : "2022-12-06T00:14:43.818Z", + "updated_at" : "2022-12-05T23:36:58.284Z", + "execution_status" : { + "last_execution_date" : "2022-12-06T00:13:43.89Z", + "last_duration" : 55, + "status" : "ok" + }, + "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef", + "id" : "b530fed0-74f5-11ed-9801-35303b735aef", + "consumer" : "alerts", "last_run" : { "alerts_count" : { "ignored" : 6, @@ -477,39 +685,75 @@ Any modifications made to this file will be overwritten. "recovered" : 5, "active" : 0 }, - "outcome_msg" : "outcome_msg", + "outcome_msg" : [ "outcome_msg", "outcome_msg" ], + "outcome_order" : 5, "warning" : "warning", "outcome" : "succeeded" }, - "api_key_created_by_user" : false, "params" : { "key" : "" }, "created_by" : "elastic", - "enabled" : true, "muted_alert_ids" : [ "muted_alert_ids", "muted_alert_ids" ], "rule_type_id" : "monitoring_alert_cluster_health", - "revision" : 5, + "revision" : 2, "tags" : [ "tags", "tags" ], - "running" : true, "api_key_owner" : "elastic", "schedule" : { "interval" : "1m" }, - "notify_when" : "onActiveAlert", - "next_run" : "2022-12-06T00:14:43.818Z", - "updated_at" : "2022-12-05T23:36:58.284Z", - "execution_status" : { - "last_execution_date" : "2022-12-06T00:13:43.89Z", - "last_duration" : 55, - "status" : "ok" - }, "name" : "cluster_health_rule", "updated_by" : "elastic", - "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef", - "id" : "b530fed0-74f5-11ed-9801-35303b735aef", "mute_all" : false, "actions" : [ { + "alerts_filter" : { + "timeframe" : { + "hours" : { + "start" : "08:00", + "end" : "17:00" + }, + "timezone" : "Europe/Madrid", + "days" : [ 1, 2, 3, 4, 5 ] + }, + "query" : { + "kql" : "kql", + "filters" : [ { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + }, { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + } ] + } + }, "id" : "9dca3e00-74f5-11ed-9801-35303b735aef", "params" : { "key" : "" @@ -523,6 +767,54 @@ Any modifications made to this file will be overwritten. }, "group" : "default" }, { + "alerts_filter" : { + "timeframe" : { + "hours" : { + "start" : "08:00", + "end" : "17:00" + }, + "timezone" : "Europe/Madrid", + "days" : [ 1, 2, 3, 4, 5 ] + }, + "query" : { + "kql" : "kql", + "filters" : [ { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + }, { + "$state" : "{}", + "meta" : { + "field" : "field", + "controlledBy" : "controlledBy", + "negate" : true, + "alias" : "alias", + "index" : "index", + "disabled" : true, + "params" : "{}", + "type" : "type", + "value" : "value", + "isMultiIndex" : true, + "key" : "key", + "group" : "group" + }, + "query" : "{}" + } ] + } + }, "id" : "9dca3e00-74f5-11ed-9801-35303b735aef", "params" : { "key" : "" @@ -535,8 +827,7 @@ Any modifications made to this file will be overwritten. "notify_when" : "onActiveAlert" }, "group" : "default" - } ], - "consumer" : "alerts" + } ] } ], "page" : 0 } @@ -655,6 +946,20 @@ Any modifications made to this file will be overwritten.
{
   "throttle" : "10m",
   "created_at" : "2022-12-05T23:36:58.284Z",
+  "api_key_created_by_user" : false,
+  "enabled" : true,
+  "running" : true,
+  "notify_when" : "onActiveAlert",
+  "next_run" : "2022-12-06T00:14:43.818Z",
+  "updated_at" : "2022-12-05T23:36:58.284Z",
+  "execution_status" : {
+    "last_execution_date" : "2022-12-06T00:13:43.89Z",
+    "last_duration" : 55,
+    "status" : "ok"
+  },
+  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "consumer" : "alerts",
   "last_run" : {
     "alerts_count" : {
       "ignored" : 6,
@@ -662,39 +967,75 @@ Any modifications made to this file will be overwritten.
       "recovered" : 5,
       "active" : 0
     },
-    "outcome_msg" : "outcome_msg",
+    "outcome_msg" : [ "outcome_msg", "outcome_msg" ],
+    "outcome_order" : 5,
     "warning" : "warning",
     "outcome" : "succeeded"
   },
-  "api_key_created_by_user" : false,
   "params" : {
     "key" : ""
   },
   "created_by" : "elastic",
-  "enabled" : true,
   "muted_alert_ids" : [ "muted_alert_ids", "muted_alert_ids" ],
   "rule_type_id" : "monitoring_alert_cluster_health",
-  "revision" : 5,
+  "revision" : 2,
   "tags" : [ "tags", "tags" ],
-  "running" : true,
   "api_key_owner" : "elastic",
   "schedule" : {
     "interval" : "1m"
   },
-  "notify_when" : "onActiveAlert",
-  "next_run" : "2022-12-06T00:14:43.818Z",
-  "updated_at" : "2022-12-05T23:36:58.284Z",
-  "execution_status" : {
-    "last_execution_date" : "2022-12-06T00:13:43.89Z",
-    "last_duration" : 55,
-    "status" : "ok"
-  },
   "name" : "cluster_health_rule",
   "updated_by" : "elastic",
-  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
-  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
   "mute_all" : false,
   "actions" : [ {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -708,6 +1049,54 @@ Any modifications made to this file will be overwritten.
     },
     "group" : "default"
   }, {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -720,8 +1109,7 @@ Any modifications made to this file will be overwritten.
       "notify_when" : "onActiveAlert"
     },
     "group" : "default"
-  } ],
-  "consumer" : "alerts"
+  } ]
 }

Produces

@@ -2017,6 +2405,20 @@ Any modifications made to this file will be overwritten.
{
   "throttle" : "10m",
   "created_at" : "2022-12-05T23:36:58.284Z",
+  "api_key_created_by_user" : false,
+  "enabled" : true,
+  "running" : true,
+  "notify_when" : "onActiveAlert",
+  "next_run" : "2022-12-06T00:14:43.818Z",
+  "updated_at" : "2022-12-05T23:36:58.284Z",
+  "execution_status" : {
+    "last_execution_date" : "2022-12-06T00:13:43.89Z",
+    "last_duration" : 55,
+    "status" : "ok"
+  },
+  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
+  "consumer" : "alerts",
   "last_run" : {
     "alerts_count" : {
       "ignored" : 6,
@@ -2024,39 +2426,75 @@ Any modifications made to this file will be overwritten.
       "recovered" : 5,
       "active" : 0
     },
-    "outcome_msg" : "outcome_msg",
+    "outcome_msg" : [ "outcome_msg", "outcome_msg" ],
+    "outcome_order" : 5,
     "warning" : "warning",
     "outcome" : "succeeded"
   },
-  "api_key_created_by_user" : false,
   "params" : {
     "key" : ""
   },
   "created_by" : "elastic",
-  "enabled" : true,
   "muted_alert_ids" : [ "muted_alert_ids", "muted_alert_ids" ],
   "rule_type_id" : "monitoring_alert_cluster_health",
-  "revision" : 5,
+  "revision" : 2,
   "tags" : [ "tags", "tags" ],
-  "running" : true,
   "api_key_owner" : "elastic",
   "schedule" : {
     "interval" : "1m"
   },
-  "notify_when" : "onActiveAlert",
-  "next_run" : "2022-12-06T00:14:43.818Z",
-  "updated_at" : "2022-12-05T23:36:58.284Z",
-  "execution_status" : {
-    "last_execution_date" : "2022-12-06T00:13:43.89Z",
-    "last_duration" : 55,
-    "status" : "ok"
-  },
   "name" : "cluster_health_rule",
   "updated_by" : "elastic",
-  "scheduled_task_id" : "b530fed0-74f5-11ed-9801-35303b735aef",
-  "id" : "b530fed0-74f5-11ed-9801-35303b735aef",
   "mute_all" : false,
   "actions" : [ {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -2070,6 +2508,54 @@ Any modifications made to this file will be overwritten.
     },
     "group" : "default"
   }, {
+    "alerts_filter" : {
+      "timeframe" : {
+        "hours" : {
+          "start" : "08:00",
+          "end" : "17:00"
+        },
+        "timezone" : "Europe/Madrid",
+        "days" : [ 1, 2, 3, 4, 5 ]
+      },
+      "query" : {
+        "kql" : "kql",
+        "filters" : [ {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        }, {
+          "$state" : "{}",
+          "meta" : {
+            "field" : "field",
+            "controlledBy" : "controlledBy",
+            "negate" : true,
+            "alias" : "alias",
+            "index" : "index",
+            "disabled" : true,
+            "params" : "{}",
+            "type" : "type",
+            "value" : "value",
+            "isMultiIndex" : true,
+            "key" : "key",
+            "group" : "group"
+          },
+          "query" : "{}"
+        } ]
+      }
+    },
     "id" : "9dca3e00-74f5-11ed-9801-35303b735aef",
     "params" : {
       "key" : ""
@@ -2082,8 +2568,7 @@ Any modifications made to this file will be overwritten.
       "notify_when" : "onActiveAlert"
     },
     "group" : "default"
-  } ],
-  "consumer" : "alerts"
+  } ]
 }

Produces

@@ -2119,6 +2604,12 @@ Any modifications made to this file will be overwritten.
  • Legacy_update_alert_request_properties_actions_inner -
  • Legacy_update_alert_request_properties_schedule -
  • actions_inner -
  • +
  • actions_inner_alerts_filter -
  • +
  • actions_inner_alerts_filter_query -
  • +
  • actions_inner_alerts_filter_query_filters_inner -
  • +
  • actions_inner_alerts_filter_query_filters_inner_meta -
  • +
  • actions_inner_alerts_filter_timeframe -
  • +
  • actions_inner_alerts_filter_timeframe_hours -
  • actions_inner_frequency -
  • alert_response_properties - Legacy alert response properties
  • alert_response_properties_executionStatus -
  • @@ -2246,7 +2737,8 @@ Any modifications made to this file will be overwritten.

    actions_inner - Up

    -
    connector_type_id (optional)
    String The type of connector. This property appears in responses but cannot be set in requests.
    +
    alerts_filter (optional)
    +
    connector_type_id (optional)
    String The type of connector. This property appears in responses but cannot be set in requests.
    frequency (optional)
    group (optional)
    String The group name for the actions. If you don't need to group actions, set to default.
    id (optional)
    String The identifier for the connector saved object.
    @@ -2254,6 +2746,66 @@ Any modifications made to this file will be overwritten.
    uuid (optional)
    String A universally unique identifier (UUID) for the action.
    +
    +

    actions_inner_alerts_filter - Up

    +
    Conditions that affect whether the action runs. If you specify multiple conditions, all conditions must be met for the action to run. For example, if an alert occurs within the specified time frame and matches the query, the action runs.
    + +
    +
    +

    actions_inner_alerts_filter_query - Up

    +
    Defines a query filter that determines whether the action runs.
    +
    +
    kql (optional)
    String A filter written in Kibana Query Language (KQL).
    +
    filters (optional)
    +
    +
    +
    +

    actions_inner_alerts_filter_query_filters_inner - Up

    +
    A filter written in Elasticsearch Query Domain Specific Language (DSL) as defined in the kbn-es-query package.
    +
    +
    meta (optional)
    +
    query (optional)
    +
    Dollarstate (optional)
    +
    +
    +
    +

    actions_inner_alerts_filter_query_filters_inner_meta - Up

    +
    +
    +
    alias (optional)
    +
    controlledBy (optional)
    +
    disabled (optional)
    +
    field (optional)
    +
    group (optional)
    +
    index (optional)
    +
    isMultiIndex (optional)
    +
    key (optional)
    +
    negate (optional)
    +
    params (optional)
    +
    type (optional)
    +
    value (optional)
    +
    +
    +
    +

    actions_inner_alerts_filter_timeframe - Up

    +
    Defines a period that limits whether the action runs.
    +
    +
    days (optional)
    array[Integer] Defines the days of the week that the action can run, represented as an array of numbers. For example, 1 represents Monday. An empty array is equivalent to specifying all the days of the week.
    +
    hours (optional)
    +
    timezone (optional)
    String The ISO time zone for the hours values. Values such as UTC and UTC+1 also work but lack built-in daylight savings time support and are not recommended.
    +
    +
    +
    +

    actions_inner_alerts_filter_timeframe_hours - Up

    +
    Defines the range of time in a day that the action can run. If the start value is 00:00 and the end value is 24:00, actions be generated all day.
    +
    +
    end (optional)
    String The end of the time frame in 24-hour notation (hh:mm).
    +
    start (optional)
    String The start of the time frame in 24-hour notation (hh:mm).
    +
    +

    actions_inner_frequency - Up

    The parameters that affect how often actions are generated. NOTE: You cannot specify these parameters when notify_when or throttle are defined at the rule level.
    @@ -2628,7 +3180,8 @@ Any modifications made to this file will be overwritten.
    alerts_count (optional)
    outcome (optional)
    -
    outcome_msg (optional)
    +
    outcome_msg (optional)
    +
    outcome_order (optional)
    warning (optional)
    diff --git a/docs/api/saved-objects/export.asciidoc b/docs/api/saved-objects/export.asciidoc index 53910835c4989..247af44e54c8b 100644 --- a/docs/api/saved-objects/export.asciidoc +++ b/docs/api/saved-objects/export.asciidoc @@ -23,7 +23,7 @@ experimental[] Retrieve sets of saved objects that you want to import into {kib} ==== Request body `type`:: - (Optional, array|string) The saved object types to include in the export. + (Optional, array|string) The saved object types to include in the export. Use `*` to export all the types. `objects`:: (Optional, array) A list of objects to export. @@ -106,7 +106,7 @@ $ curl -X POST api/saved_objects/_export -H 'kbn-xsrf: true' -H 'Content-Type: a -------------------------------------------------- // KIBANA -Export a specific saved object and it's related objects : +Export a specific saved object and it's related objects: [source,sh] -------------------------------------------------- @@ -122,3 +122,15 @@ $ curl -X POST api/saved_objects/_export -H 'kbn-xsrf: true' -H 'Content-Type: a }' -------------------------------------------------- // KIBANA + +Export all exportable saved objects and their related objects: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/saved_objects/_export -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "type": "*", + "includeReferencesDeep": true +}' +-------------------------------------------------- +// KIBANA \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/index.ts index 498c5ab00d6c9..b0156db86ba8a 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/index.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +export { SavedObjectsImporter, SavedObjectsImportError } from './src/import'; export { SavedObjectsExporter, SavedObjectsExportError, - SavedObjectsImporter, - SavedObjectsImportError, -} from './src'; + EXPORT_ALL_TYPES_TOKEN, +} from './src/export'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/constants.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/constants.ts new file mode 100644 index 0000000000000..cc178ca858272 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Token used for the `type` option when exporting by type + * to specify that all (importable and exportable) types should be exported. + */ +export const EXPORT_ALL_TYPES_TOKEN = '*'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/index.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/index.ts index 268bc6d007595..581e244ced7fe 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/index.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/index.ts @@ -8,3 +8,4 @@ export { SavedObjectsExporter } from './saved_objects_exporter'; export { SavedObjectsExportError } from './errors'; +export { EXPORT_ALL_TYPES_TOKEN } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts index 2bb65c891d837..833f0e3d08158 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts @@ -7,18 +7,27 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObject } from '@kbn/core-saved-objects-server'; +import type { SavedObject, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; +import { EXPORT_ALL_TYPES_TOKEN } from './constants'; async function readStreamToCompletion(stream: Readable): Promise>> { return createPromiseFromStreams([stream, createConcatStream([])]); } +const createType = (parts: Partial): SavedObjectsType => ({ + name: 'type', + namespaceType: 'single', + hidden: false, + mappings: { properties: {} }, + ...parts, +}); + const exportSizeLimit = 10000; const request = httpServerMock.createKibanaRequest(); @@ -32,13 +41,17 @@ describe('getSortedObjectsForExport()', () => { logger = loggerMock.create(); typeRegistry = new SavedObjectTypeRegistry(); savedObjectsClient = savedObjectsClientMock.create(); - exporter = new SavedObjectsExporter({ + exporter = createExporter(); + }); + + const createExporter = () => { + return new SavedObjectsExporter({ exportSizeLimit, logger, savedObjectsClient, typeRegistry, }); - }); + }; describe('#exportByTypes', () => { test('exports selected types and sorts them', async () => { @@ -986,6 +999,45 @@ describe('getSortedObjectsForExport()', () => { ] `); }); + + test(`supports the "all types" wildcard`, async () => { + typeRegistry.registerType( + createType({ + name: 'exportable_1', + management: { importableAndExportable: true }, + }) + ); + typeRegistry.registerType( + createType({ + name: 'exportable_2', + management: { importableAndExportable: true }, + }) + ); + typeRegistry.registerType( + createType({ + name: 'not_exportable', + management: { importableAndExportable: false }, + }) + ); + + exporter = createExporter(); + + savedObjectsClient.find.mockResolvedValueOnce({ + total: 0, + per_page: 1000, + page: 1, + saved_objects: [], + }); + + await exporter.exportByTypes({ request, types: [EXPORT_ALL_TYPES_TOKEN] }); + + expect(savedObjectsClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.createPointInTimeFinder).toHaveBeenCalledWith( + expect.objectContaining({ + type: ['exportable_1', 'exportable_2'], + }) + ); + }); }); describe('#exportByObjects', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts index f5f735e3b6c5d..7d4f56aaa238f 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts @@ -30,6 +30,7 @@ import { getPreservedOrderComparator, type SavedObjectComparator, } from './utils'; +import { EXPORT_ALL_TYPES_TOKEN } from './constants'; /** * @internal @@ -39,6 +40,7 @@ export class SavedObjectsExporter implements ISavedObjectsExporter { readonly #exportSizeLimit: number; readonly #typeRegistry: ISavedObjectTypeRegistry; readonly #log: Logger; + readonly #exportableTypes: string[]; constructor({ savedObjectsClient, @@ -55,6 +57,9 @@ export class SavedObjectsExporter implements ISavedObjectsExporter { this.#savedObjectsClient = savedObjectsClient; this.#exportSizeLimit = exportSizeLimit; this.#typeRegistry = typeRegistry; + this.#exportableTypes = this.#typeRegistry + .getImportableAndExportableTypes() + .map((type) => type.name); } public async exportByTypes(options: SavedObjectsExportByTypeOptions) { @@ -146,6 +151,10 @@ export class SavedObjectsExporter implements ISavedObjectsExporter { hasReference, search, }: SavedObjectsExportByTypeOptions) { + if (types.includes(EXPORT_ALL_TYPES_TOKEN)) { + types = this.#exportableTypes; + } + const finder = this.#savedObjectsClient.createPointInTimeFinder({ type: types, hasReference, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts index c002e12363af6..0a630fe18d353 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts @@ -30,6 +30,7 @@ import { kibanaResponseFactory } from '@kbn/core-http-router-server-internal'; import { typeRegistryInstanceMock } from '../saved_objects_service.test.mocks'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; +import { EXPORT_ALL_TYPES_TOKEN } from '@kbn/core-saved-objects-import-export-server-internal'; async function readStreamToCompletion(stream: Readable) { return createPromiseFromStreams([stream, createConcatStream([])]); @@ -148,6 +149,20 @@ describe('validateTypes', () => { expect(validateTypes(allowedTypes, allowedTypes)).toBeUndefined(); expect(validateTypes(['config'], allowedTypes)).toBeUndefined(); }); + it('supports the all types token', () => { + expect(validateTypes([EXPORT_ALL_TYPES_TOKEN], allowedTypes)).toBeUndefined(); + expect(validateTypes([EXPORT_ALL_TYPES_TOKEN, allowedTypes[0]], allowedTypes)).toBeUndefined(); + }); + it('returns an error message for non-allowed types even with the all types token', () => { + expect( + validateTypes( + [EXPORT_ALL_TYPES_TOKEN, 'not-allowed-type', 'not-allowed-type-2'], + allowedTypes + ) + ).toMatchInlineSnapshot( + `"Trying to export non-exportable type(s): not-allowed-type, not-allowed-type-2"` + ); + }); }); describe('validateObjects', () => { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts index 7daea1a33237e..454345f322df6 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts @@ -23,7 +23,8 @@ import { SavedObjectsExportResultDetails, SavedObjectsErrorHelpers, } from '@kbn/core-saved-objects-server'; -import { Logger } from '@kbn/logging'; +import type { Logger } from '@kbn/logging'; +import { EXPORT_ALL_TYPES_TOKEN } from '@kbn/core-saved-objects-import-export-server-internal'; export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { const savedObjects = await createPromiseFromStreams([ @@ -43,7 +44,9 @@ export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) } export function validateTypes(types: string[], supportedTypes: string[]): string | undefined { - const invalidTypes = types.filter((t) => !supportedTypes.includes(t)); + const invalidTypes = types.filter( + (t) => t !== EXPORT_ALL_TYPES_TOKEN && !supportedTypes.includes(t) + ); if (invalidTypes.length) { return `Trying to export non-exportable type(s): ${invalidTypes.join(', ')}`; } @@ -109,6 +112,7 @@ export function throwOnGloballyHiddenTypes( ); } } + /** * @param {string[]} unsupportedTypes saved object types registered with hidden=false and hiddenFromHttpApis=true */ @@ -120,6 +124,7 @@ export function throwOnHttpHiddenTypes(unsupportedTypes: string[]) { ); } } + /** * @param {string[]} type saved object type * @param {ISavedObjectTypeRegistry} registry the saved object type registry @@ -147,6 +152,7 @@ export function throwIfAnyTypeNotVisibleByAPI( throwOnHttpHiddenTypes(unsupportedTypes); } } + export interface BulkGetItem { type: string; id: string; @@ -168,6 +174,7 @@ export interface LogWarnOnExternalRequest { req: KibanaRequest; logger: Logger; } + /** * Only log a warning when the request is internal * Allows us to silence the logs for development diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 8196478232792..6ed36c08f584f 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2164,6 +2164,10 @@ } } }, + "threshold-explorer-view": { + "dynamic": false, + "properties": {} + }, "observability-onboarding-state": { "properties": { "state": { diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 06733b6ed5b12..b7000e553a2b1 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -94,7 +94,7 @@ pageLoadAssetSize: monitoring: 80000 navigation: 37269 newsfeed: 42228 - observability: 95000 + observability: 100000 observabilityOnboarding: 19573 observabilityShared: 52256 osquery: 107090 diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 7184ce0960f63..97b73ce52f6cb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -81,7 +81,7 @@ describe('checking migration metadata changes on all registered SO types', () => "connector_token": "5a9ac29fe9c740eb114e9c40517245c71706b005", "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654", - "dashboard": "cf7c9c2334decab716fe519780cb4dc52967a91d", + "dashboard": "1635368413415b340ae6f43fcd0a55c5dcdd4f41", "endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b", "enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d", "epm-packages": "2449bb565f987eff70b1b39578bb17e90c404c6e", @@ -147,6 +147,7 @@ describe('checking migration metadata changes on all registered SO types', () => "tag": "e2544392fe6563e215bb677abc8b01c2601ef2dc", "task": "04f30bd7bae923f3a53c31ab3b9745a93872fc02", "telemetry": "7b00bcf1c7b4f6db1192bb7405a6a63e78b699fd", + "threshold-explorer-view": "175306806f9fc8e13fcc1c8953ec4ba89bda1b70", "ui-metric": "d227284528fd19904e9d972aea0a13716fc5fe24", "upgrade-assistant-ml-upgrade-operation": "421f52731cb24e242d70672ba4725e169277efb3", "upgrade-assistant-reindex-operation": "01f3c3e051659ace56492a73928987e717537a93", 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 755ed052c2dc6..14f3a60e51506 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 @@ -265,6 +265,7 @@ describe('split .kibana index into multiple system indices', () => { "synthetics-privates-locations", "tag", "telemetry", + "threshold-explorer-view", "ui-metric", "upgrade-assistant-ml-upgrade-operation", "upgrade-assistant-reindex-operation", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index ed6cd97136781..0bb0bad86da98 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -123,6 +123,7 @@ const previouslyRegisteredTypes = [ 'telemetry', 'timelion-sheet', 'tsvb-validation-telemetry', + 'threshold-explorer-view', 'ui-counter', 'ui-metric', 'upgrade-assistant-ml-upgrade-operation', diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 14ebf681c0fec..6c30d758e470a 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -307,6 +307,7 @@ kibana_vars=( xpack.observability.unsafe.alertDetails.metrics.enabled xpack.observability.unsafe.alertDetails.logs.enabled xpack.observability.unsafe.alertDetails.uptime.enabled + xpack.observability.unsafe.thresholdRule.enabled xpack.reporting.capture.browser.autoDownload xpack.reporting.capture.browser.chromium.disableSandbox xpack.reporting.capture.browser.chromium.inspect diff --git a/src/plugins/controls/common/control_group/control_group_persistence.ts b/src/plugins/controls/common/control_group/control_group_persistence.ts index 1dbe096307c50..cf108b1a4304a 100644 --- a/src/plugins/controls/common/control_group/control_group_persistence.ts +++ b/src/plugins/controls/common/control_group/control_group_persistence.ts @@ -105,7 +105,7 @@ export const controlGroupInputToRawControlGroupAttributes = ( export const rawControlGroupAttributesToControlGroupInput = ( rawControlGroupAttributes: RawControlGroupAttributes -): Omit | undefined => { +): PersistableControlGroupInput | undefined => { const defaultControlGroupInput = getDefaultControlGroupInput(); const { chainingSystem, controlStyle, ignoreParentSettingsJSON, panelsJSON } = rawControlGroupAttributes; diff --git a/src/plugins/dashboard/common/bwc/types.ts b/src/plugins/dashboard/common/bwc/types.ts index 5a7d30dd085df..598bde8ed3050 100644 --- a/src/plugins/dashboard/common/bwc/types.ts +++ b/src/plugins/dashboard/common/bwc/types.ts @@ -8,29 +8,28 @@ import type { SavedObjectReference } from '@kbn/core/public'; import type { Serializable } from '@kbn/utility-types'; +import { GridData } from '../content_management'; -import { GridData } from '..'; - -interface SavedObjectAttributes { +interface KibanaAttributes { kibanaSavedObjectMeta: { searchSourceJSON: string; }; } -interface Doc { +interface Doc { references: SavedObjectReference[]; attributes: Attributes; id: string; type: string; } -interface DocPre700 { +interface DocPre700 { attributes: Attributes; id: string; type: string; } -interface DashboardAttributes extends SavedObjectAttributes { +interface DashboardAttributes extends KibanaAttributes { panelsJSON: string; description: string; version: number; @@ -40,7 +39,7 @@ interface DashboardAttributes extends SavedObjectAttributes { optionsJSON?: string; } -interface DashboardAttributesTo720 extends SavedObjectAttributes { +interface DashboardAttributesTo720 extends KibanaAttributes { panelsJSON: string; description: string; uiStateJSON?: string; diff --git a/src/plugins/dashboard/common/content_management/cm_services.ts b/src/plugins/dashboard/common/content_management/cm_services.ts new file mode 100644 index 0000000000000..14f6a731e1402 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/cm_services.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { + ContentManagementServicesDefinition as ServicesDefinition, + Version, +} from '@kbn/object-versioning'; + +// We export the versioned service definition from this file and not the barrel to avoid adding +// the schemas in the "public" js bundle + +import { serviceDefinition as v1 } from './v1/cm_services'; + +export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = { + 1: v1, +}; diff --git a/src/plugins/dashboard/common/content_management/constants.ts b/src/plugins/dashboard/common/content_management/constants.ts new file mode 100644 index 0000000000000..dcbb1ac7d6974 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LATEST_VERSION = 1; + +export const CONTENT_ID = 'dashboard'; diff --git a/src/plugins/dashboard/common/content_management/index.ts b/src/plugins/dashboard/common/content_management/index.ts new file mode 100644 index 0000000000000..5f0a06d35f734 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { LATEST_VERSION, CONTENT_ID } from './constants'; + +export type { DashboardContentType } from './types'; + +export type { + GridData, + DashboardItem, + DashboardCrudTypes, + DashboardAttributes, + SavedDashboardPanel, +} from './latest'; + +// Today "v1" === "latest" so the export under DashboardV1 namespace is not really useful +// We leave it as a reference for future version when it will be needed to export/support older types +// in the UIs. +export * as DashboardV1 from './v1'; diff --git a/src/plugins/dashboard/common/content_management/latest.ts b/src/plugins/dashboard/common/content_management/latest.ts new file mode 100644 index 0000000000000..f02d0a348e543 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/latest.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Latest version is 1 +export * from './v1'; diff --git a/src/plugins/dashboard/common/content_management/types.ts b/src/plugins/dashboard/common/content_management/types.ts new file mode 100644 index 0000000000000..7ed0122c0a8b6 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type DashboardContentType = 'dashboard'; diff --git a/src/plugins/dashboard/common/content_management/v1/cm_services.ts b/src/plugins/dashboard/common/content_management/v1/cm_services.ts new file mode 100644 index 0000000000000..3de67a1dd3cd9 --- /dev/null +++ b/src/plugins/dashboard/common/content_management/v1/cm_services.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { schema } from '@kbn/config-schema'; +import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning'; +import { + savedObjectSchema, + objectTypeToGetResultSchema, + createOptionsSchemas, + createResultSchema, +} from '@kbn/content-management-utils'; + +const dashboardAttributesSchema = schema.object( + { + // General + title: schema.string(), + description: schema.string({ defaultValue: '' }), + + // Search + kibanaSavedObjectMeta: schema.object({ + searchSourceJSON: schema.maybe(schema.string()), + }), + + // Time + timeRestore: schema.maybe(schema.boolean()), + timeFrom: schema.maybe(schema.string()), + timeTo: schema.maybe(schema.string()), + refreshInterval: schema.maybe( + schema.object({ + pause: schema.boolean(), + value: schema.number(), + display: schema.maybe(schema.string()), + section: schema.maybe(schema.number()), + }) + ), + + // Dashboard Content + controlGroupInput: schema.maybe( + schema.object({ + panelsJSON: schema.maybe(schema.string()), + controlStyle: schema.maybe(schema.string()), + chainingSystem: schema.maybe(schema.string()), + ignoreParentSettingsJSON: schema.maybe(schema.string()), + }) + ), + panelsJSON: schema.string({ defaultValue: '[]' }), + optionsJSON: schema.string({ defaultValue: '{}' }), + + // Legacy + hits: schema.maybe(schema.number()), + version: schema.maybe(schema.number()), + }, + { unknowns: 'forbid' } +); + +const dashboardSavedObjectSchema = savedObjectSchema(dashboardAttributesSchema); + +const searchOptionsSchema = schema.maybe( + schema.object( + { + onlyTitle: schema.maybe(schema.boolean()), + }, + { unknowns: 'forbid' } + ) +); + +const createOptionsSchema = schema.object({ + id: schema.maybe(createOptionsSchemas.id), + overwrite: schema.maybe(createOptionsSchemas.overwrite), + references: schema.maybe(createOptionsSchemas.references), +}); + +// Content management service definition. +// We need it for BWC support between different versions of the content +export const serviceDefinition: ServicesDefinition = { + get: { + out: { + result: { + schema: objectTypeToGetResultSchema(dashboardSavedObjectSchema), + }, + }, + }, + create: { + in: { + options: { + schema: createOptionsSchema, + }, + data: { + schema: dashboardAttributesSchema, + }, + }, + out: { + result: { + schema: createResultSchema(dashboardSavedObjectSchema), + }, + }, + }, + update: { + in: { + options: { + schema: createOptionsSchema, // same schema as "create" + }, + data: { + schema: dashboardAttributesSchema, + }, + }, + }, + search: { + in: { + options: { + schema: searchOptionsSchema, + }, + }, + }, + mSearch: { + out: { + result: { + schema: dashboardSavedObjectSchema, + }, + }, + }, +}; diff --git a/src/plugins/dashboard/common/content_management/v1/index.ts b/src/plugins/dashboard/common/content_management/v1/index.ts new file mode 100644 index 0000000000000..ecfe16506e3ee --- /dev/null +++ b/src/plugins/dashboard/common/content_management/v1/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardCrudTypes } from './types'; +export type { + GridData, + DashboardCrudTypes, + DashboardAttributes, + SavedDashboardPanel, +} from './types'; +export type DashboardItem = DashboardCrudTypes['Item']; diff --git a/src/plugins/dashboard/common/dashboard_saved_object/types.ts b/src/plugins/dashboard/common/content_management/v1/types.ts similarity index 63% rename from src/plugins/dashboard/common/dashboard_saved_object/types.ts rename to src/plugins/dashboard/common/content_management/v1/types.ts index 9d859ef660e8d..9d89d724ed00f 100644 --- a/src/plugins/dashboard/common/dashboard_saved_object/types.ts +++ b/src/plugins/dashboard/common/content_management/v1/types.ts @@ -6,38 +6,39 @@ * Side Public License, v 1. */ +import type { + ContentManagementCrudTypes, + SavedObjectCreateOptions, + SavedObjectUpdateOptions, +} from '@kbn/content-management-utils'; +import { Serializable } from '@kbn/utility-types'; import { RefreshInterval } from '@kbn/data-plugin/common'; import { RawControlGroupAttributes } from '@kbn/controls-plugin/common'; -import { Serializable } from '@kbn/utility-types'; -import { DashboardOptions, GridData } from '../types'; +import { DashboardContentType } from '../types'; + +export type DashboardCrudTypes = ContentManagementCrudTypes< + DashboardContentType, + DashboardAttributes, + Pick, + Pick, + { + /** Flag to indicate to only search the text on the "title" field */ + onlyTitle?: boolean; + } +>; /** - * The attributes of the dashboard saved object. This interface should be the - * source of truth for the latest dashboard attributes shape after all migrations. + * Grid type for React Grid Layout */ -export interface DashboardAttributes { - controlGroupInput?: RawControlGroupAttributes; - refreshInterval?: RefreshInterval; - timeRestore: boolean; - optionsJSON?: string; - useMargins?: boolean; - description: string; - panelsJSON: string; - timeFrom?: string; - version: number; - timeTo?: string; - title: string; - kibanaSavedObjectMeta: { - searchSourceJSON: string; - }; +export interface GridData { + w: number; + h: number; + x: number; + y: number; + i: string; } -export type ParsedDashboardAttributes = Omit & { - panels: SavedDashboardPanel[]; - options: DashboardOptions; -}; - /** * A saved dashboard panel parsed directly from the Dashboard Attributes panels JSON */ @@ -51,3 +52,21 @@ export interface SavedDashboardPanel { version: string; title?: string; } + +/* eslint-disable-next-line @typescript-eslint/consistent-type-definitions */ +export type DashboardAttributes = { + controlGroupInput?: RawControlGroupAttributes; + refreshInterval?: RefreshInterval; + timeRestore: boolean; + optionsJSON?: string; + useMargins?: boolean; + description: string; + panelsJSON: string; + timeFrom?: string; + version: number; + timeTo?: string; + title: string; + kibanaSavedObjectMeta: { + searchSourceJSON: string; + }; +}; diff --git a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.test.ts b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.test.ts index a6411cef1ebb8..bd057d7d21f21 100644 --- a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.test.ts +++ b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.test.ts @@ -8,11 +8,12 @@ import { createExtract, createInject } from './dashboard_container_references'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; -import { DashboardContainerStateWithType } from '../../types'; +import { ParsedDashboardAttributesWithType } from '../../types'; +import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/public'; const persistableStateService = createEmbeddablePersistableStateServiceMock(); -const dashboardWithExtractedPanel: DashboardContainerStateWithType = { +const dashboardWithExtractedPanel: ParsedDashboardAttributesWithType = { id: 'id', type: 'dashboard', panels: { @@ -33,7 +34,7 @@ const extractedSavedObjectPanelRef = { id: 'object-id', }; -const unextractedDashboardState: DashboardContainerStateWithType = { +const unextractedDashboardState: ParsedDashboardAttributesWithType = { id: 'id', type: 'dashboard', panels: { @@ -56,7 +57,7 @@ describe('inject/extract by reference panel', () => { const injected = inject( dashboardWithExtractedPanel, references - ) as DashboardContainerStateWithType; + ) as ParsedDashboardAttributesWithType; expect(injected).toEqual(unextractedDashboardState); }); @@ -71,7 +72,7 @@ describe('inject/extract by reference panel', () => { }); }); -const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = { +const dashboardWithExtractedByValuePanel: ParsedDashboardAttributesWithType = { id: 'id', type: 'dashboard', panels: { @@ -81,7 +82,7 @@ const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = { explicitInput: { id: 'panel_1', extracted_reference: 'ref', - }, + } as Partial & { id: string; extracted_reference: string }, }, }, }; @@ -92,7 +93,7 @@ const extractedByValueRef = { type: 'panel_type', }; -const unextractedDashboardByValueState: DashboardContainerStateWithType = { +const unextractedDashboardByValueState: ParsedDashboardAttributesWithType = { id: 'id', type: 'dashboard', panels: { @@ -102,7 +103,7 @@ const unextractedDashboardByValueState: DashboardContainerStateWithType = { explicitInput: { id: 'panel_1', value: 'id', - }, + } as Partial & { id: string; value: string }, }, }, }; diff --git a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts index a74afc07c4738..7b4d682085352 100644 --- a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts +++ b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts @@ -8,13 +8,14 @@ import { EmbeddableInput, - EmbeddablePersistableStateService, EmbeddableStateWithType, + EmbeddablePersistableStateService, } from '@kbn/embeddable-plugin/common'; -import { SavedObjectReference } from '@kbn/core/types'; +import { Reference } from '@kbn/content-management-utils'; import { CONTROL_GROUP_TYPE, PersistableControlGroupInput } from '@kbn/controls-plugin/common'; + import { DashboardPanelState } from '../types'; -import { DashboardContainerStateWithType } from '../../types'; +import { ParsedDashboardAttributesWithType } from '../../types'; const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`; @@ -24,8 +25,10 @@ const controlGroupId = 'dashboard_control_group'; export const createInject = ( persistableStateService: EmbeddablePersistableStateService ): EmbeddablePersistableStateService['inject'] => { - return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => { - const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType; + return (state: EmbeddableStateWithType, references: Reference[]) => { + const workingState = { ...state } as + | EmbeddableStateWithType + | ParsedDashboardAttributesWithType; if ('panels' in workingState) { workingState.panels = { ...workingState.panels }; @@ -103,9 +106,11 @@ export const createExtract = ( persistableStateService: EmbeddablePersistableStateService ): EmbeddablePersistableStateService['extract'] => { return (state: EmbeddableStateWithType) => { - const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType; + const workingState = { ...state } as + | EmbeddableStateWithType + | ParsedDashboardAttributesWithType; - const references: SavedObjectReference[] = []; + const references: Reference[] = []; if ('panels' in workingState) { workingState.panels = { ...workingState.panels }; @@ -125,7 +130,6 @@ export const createExtract = ( }); delete panel.explicitInput.savedObjectId; - delete panel.explicitInput.type; } const { state: panelState, references: panelReferences } = persistableStateService.extract({ diff --git a/src/plugins/dashboard/common/dashboard_container/types.ts b/src/plugins/dashboard/common/dashboard_container/types.ts index 2af75ccbf7fc4..139277be9315f 100644 --- a/src/plugins/dashboard/common/dashboard_container/types.ts +++ b/src/plugins/dashboard/common/dashboard_container/types.ts @@ -17,7 +17,8 @@ import { RefreshInterval } from '@kbn/data-plugin/common'; import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; -import { DashboardOptions, GridData } from '../types'; +import { DashboardOptions } from '../types'; +import { GridData } from '../content_management'; export interface DashboardPanelMap { [key: string]: DashboardPanelState; diff --git a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts index 7a8f28a293c67..c43654187634d 100644 --- a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts +++ b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts @@ -9,8 +9,7 @@ import { extractReferences, injectReferences, - InjectDeps, - ExtractDeps, + InjectExtractDeps, } from './dashboard_saved_object_references'; import { @@ -18,6 +17,7 @@ import { createInject, } from '../../dashboard_container/persistable_state/dashboard_container_references'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; +import { DashboardAttributes } from '../../content_management'; const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock(); const dashboardInject = createInject(embeddablePersistableStateServiceMock); @@ -38,15 +38,25 @@ embeddablePersistableStateServiceMock.inject.mockImplementation((state, referenc return state; }); -const deps: InjectDeps & ExtractDeps = { +const deps: InjectExtractDeps = { embeddablePersistableStateService: embeddablePersistableStateServiceMock, }; +const commonAttributes: DashboardAttributes = { + kibanaSavedObjectMeta: { searchSourceJSON: '' }, + timeRestore: false, + panelsJSON: '', + version: 1, + description: '', + title: '', +}; + describe('legacy extract references', () => { test('extracts references from panelsJSON', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -70,8 +80,15 @@ describe('legacy extract references', () => { expect(updatedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { + "description": "", "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_0\\"},{\\"title\\":\\"Title 2\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_1\\"}]", + "timeRestore": false, + "title": "", + "version": 1, }, "references": Array [ Object { @@ -93,6 +110,7 @@ describe('legacy extract references', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -113,6 +131,7 @@ describe('legacy extract references', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -127,8 +146,15 @@ describe('legacy extract references', () => { expect(extractReferences(doc, deps)).toMatchInlineSnapshot(` Object { "attributes": Object { + "description": "", "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", + "timeRestore": false, + "title": "", + "version": 1, }, "references": Array [], } @@ -214,6 +240,7 @@ describe('extractReferences', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -239,8 +266,15 @@ describe('extractReferences', () => { expect(updatedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { + "description": "", "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_panel-1\\"},{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-2\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_panel-2\\"}]", + "timeRestore": false, + "title": "", + "version": 1, }, "references": Array [ Object { @@ -262,6 +296,7 @@ describe('extractReferences', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -274,7 +309,7 @@ describe('extractReferences', () => { references: [], }; expect(() => extractReferences(doc, deps)).toThrowErrorMatchingInlineSnapshot( - `"\\"type\\" attribute is missing from panel \\"0\\""` + `"\\"type\\" attribute is missing from panel \\"undefined\\""` ); }); @@ -282,6 +317,7 @@ describe('extractReferences', () => { const doc = { id: '1', attributes: { + ...commonAttributes, foo: true, panelsJSON: JSON.stringify([ { @@ -296,8 +332,15 @@ describe('extractReferences', () => { expect(extractReferences(doc, deps)).toMatchInlineSnapshot(` Object { "attributes": Object { + "description": "", "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", + "timeRestore": false, + "title": "", + "version": 1, }, "references": Array [], } @@ -308,6 +351,7 @@ describe('extractReferences', () => { describe('injectReferences', () => { test('returns injected attributes', () => { const attributes = { + ...commonAttributes, id: '1', title: 'test', panelsJSON: JSON.stringify([ @@ -339,9 +383,15 @@ describe('injectReferences', () => { expect(newAttributes).toMatchInlineSnapshot(` Object { + "description": "", "id": "1", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, "panelsJSON": "[{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]", + "timeRestore": false, "title": "test", + "version": 1, } `); }); @@ -350,11 +400,12 @@ describe('injectReferences', () => { const attributes = { id: '1', title: 'test', - }; + } as unknown as DashboardAttributes; const newAttributes = injectReferences({ attributes, references: [] }, deps); expect(newAttributes).toMatchInlineSnapshot(` Object { "id": "1", + "panelsJSON": "[]", "title": "test", } `); @@ -362,6 +413,7 @@ describe('injectReferences', () => { test('skips when panelsJSON is not an array', () => { const attributes = { + ...commonAttributes, id: '1', panelsJSON: '{}', title: 'test', @@ -369,15 +421,22 @@ describe('injectReferences', () => { const newAttributes = injectReferences({ attributes, references: [] }, deps); expect(newAttributes).toMatchInlineSnapshot(` Object { + "description": "", "id": "1", - "panelsJSON": "{}", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, + "panelsJSON": "[]", + "timeRestore": false, "title": "test", + "version": 1, } `); }); test('skips a panel when panelRefName is missing', () => { const attributes = { + ...commonAttributes, id: '1', title: 'test', panelsJSON: JSON.stringify([ @@ -400,15 +459,22 @@ describe('injectReferences', () => { const newAttributes = injectReferences({ attributes, references }, deps); expect(newAttributes).toMatchInlineSnapshot(` Object { + "description": "", "id": "1", - "panelsJSON": "[{\\"version\\":\\"\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "", + }, + "panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]", + "timeRestore": false, "title": "test", + "version": 1, } `); }); test(`fails when it can't find the reference in the array`, () => { const attributes = { + ...commonAttributes, id: '1', title: 'test', panelsJSON: JSON.stringify([ diff --git a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts index 88017bb6d550e..f8a1c089d2fc3 100644 --- a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts +++ b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts @@ -7,130 +7,66 @@ */ import semverGt from 'semver/functions/gt'; -import { - RawControlGroupAttributes, - PersistableControlGroupInput, -} from '@kbn/controls-plugin/common'; -import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types'; +import { Reference } from '@kbn/content-management-utils'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types'; +import { rawControlGroupAttributesToControlGroupInput } from '@kbn/controls-plugin/common'; -import { SavedDashboardPanel } from '../types'; import { - convertPanelStateToSavedDashboardPanel, - convertSavedDashboardPanelToPanelState, + convertPanelMapToSavedPanels, + convertSavedPanelsToPanelMap, } from '../../lib/dashboard_panel_converters'; -import { DashboardPanelState } from '../../dashboard_container/types'; -import { DashboardContainerStateWithType } from '../../types'; - -export interface ExtractDeps { - embeddablePersistableStateService: EmbeddablePersistableStateService; -} +import { DashboardAttributesAndReferences, ParsedDashboardAttributesWithType } from '../../types'; +import { DashboardAttributes, SavedDashboardPanel } from '../../content_management'; -export interface InjectDeps { +export interface InjectExtractDeps { embeddablePersistableStateService: EmbeddablePersistableStateService; } -interface SavedObjectAttributesAndReferences { - attributes: SavedObjectAttributes; - references: SavedObjectReference[]; -} - const isPre730Panel = (panel: Record): boolean => { - return 'version' in panel ? semverGt('7.3.0', panel.version) : true; + return 'version' in panel && panel.version ? semverGt('7.3.0', panel.version) : true; }; -function dashboardAttributesToState(attributes: SavedObjectAttributes): { - state: DashboardContainerStateWithType; - panels: SavedDashboardPanel[]; -} { - let inputPanels = [] as SavedDashboardPanel[]; +function parseDashboardAttributesWithType( + attributes: DashboardAttributes +): ParsedDashboardAttributesWithType { + let parsedPanels = [] as SavedDashboardPanel[]; if (typeof attributes.panelsJSON === 'string') { - inputPanels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; - } - - let controlGroupInput: PersistableControlGroupInput | undefined; - if (attributes.controlGroupInput) { - const rawControlGroupInput = - attributes.controlGroupInput as unknown as RawControlGroupAttributes; - if (rawControlGroupInput.panelsJSON && typeof rawControlGroupInput.panelsJSON === 'string') { - const controlGroupPanels = JSON.parse(rawControlGroupInput.panelsJSON); - if (controlGroupPanels && typeof controlGroupPanels === 'object') { - controlGroupInput = { - ...rawControlGroupInput, - panels: controlGroupPanels, - }; - } + const parsedJSON = JSON.parse(attributes.panelsJSON); + if (Array.isArray(parsedJSON)) { + parsedPanels = parsedJSON as SavedDashboardPanel[]; } } return { - panels: inputPanels, - state: { - id: attributes.id as string, - controlGroupInput, - type: 'dashboard', - panels: inputPanels.reduce>((current, panel, index) => { - const panelIndex = panel.panelIndex || `${index}`; - current[panelIndex] = convertSavedDashboardPanelToPanelState(panel); - return current; - }, {}), - }, - }; -} - -function panelStatesToPanels( - panelStates: DashboardContainerStateWithType['panels'], - originalPanels: SavedDashboardPanel[] -): SavedDashboardPanel[] { - return Object.entries(panelStates).map(([id, panelState]) => { - // Find matching original panel to get the version - let originalPanel = originalPanels.find((p) => p.panelIndex === id); - - if (!originalPanel) { - // Maybe original panel doesn't have a panel index and it's just straight up based on its index - const numericId = parseInt(id, 10); - originalPanel = isNaN(numericId) ? originalPanel : originalPanels[numericId]; - } - - return convertPanelStateToSavedDashboardPanel( - panelState, - originalPanel?.version ? originalPanel.version : '' - ); - }); + controlGroupInput: + attributes.controlGroupInput && + rawControlGroupAttributesToControlGroupInput(attributes.controlGroupInput), + type: 'dashboard', + panels: convertSavedPanelsToPanelMap(parsedPanels), + } as ParsedDashboardAttributesWithType; } export function injectReferences( - { attributes, references = [] }: SavedObjectAttributesAndReferences, - deps: InjectDeps -): SavedObjectAttributes { - // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when - // importing objects without panelsJSON. At development time of this, there is no guarantee each saved - // object has panelsJSON in all previous versions of kibana. - if (typeof attributes.panelsJSON !== 'string') { - return attributes; - } - const parsedPanels = JSON.parse(attributes.panelsJSON); - // Same here, prevent failing saved object import if ever panels aren't an array. - if (!Array.isArray(parsedPanels)) { - return attributes; - } - - const { panels, state } = dashboardAttributesToState(attributes); + { attributes, references = [] }: DashboardAttributesAndReferences, + deps: InjectExtractDeps +): DashboardAttributes { + const parsedAttributes = parseDashboardAttributesWithType(attributes); + // inject references back into panels via the Embeddable persistable state service. const injectedState = deps.embeddablePersistableStateService.inject( - state, + parsedAttributes, references - ) as DashboardContainerStateWithType; - const injectedPanels = panelStatesToPanels(injectedState.panels, panels); + ) as ParsedDashboardAttributesWithType; + const injectedPanels = convertPanelMapToSavedPanels(injectedState.panels); const newAttributes = { ...attributes, panelsJSON: JSON.stringify(injectedPanels), - } as SavedObjectAttributes; + } as DashboardAttributes; - if (injectedState.controlGroupInput) { + if (attributes.controlGroupInput && injectedState.controlGroupInput) { newAttributes.controlGroupInput = { - ...(attributes.controlGroupInput as SavedObjectAttributes), + ...attributes.controlGroupInput, panelsJSON: JSON.stringify(injectedState.controlGroupInput.panels), }; } @@ -139,41 +75,39 @@ export function injectReferences( } export function extractReferences( - { attributes, references = [] }: SavedObjectAttributesAndReferences, - deps: ExtractDeps -): SavedObjectAttributesAndReferences { - if (typeof attributes.panelsJSON !== 'string') { - return { attributes, references }; - } + { attributes, references = [] }: DashboardAttributesAndReferences, + deps: InjectExtractDeps +): DashboardAttributesAndReferences { + const parsedAttributes = parseDashboardAttributesWithType(attributes); - const { panels, state } = dashboardAttributesToState(attributes); - if (!Array.isArray(panels)) { - return { attributes, references }; - } + const panels = parsedAttributes.panels; - if ((panels as unknown as Array>).some(isPre730Panel)) { - return pre730ExtractReferences({ attributes, references }, deps); + if ((Object.values(panels) as unknown as Array>).some(isPre730Panel)) { + return pre730ExtractReferences({ attributes, references }); } - const missingTypeIndex = panels.findIndex((panel) => panel.type === undefined); - if (missingTypeIndex >= 0) { - throw new Error(`"type" attribute is missing from panel "${missingTypeIndex}"`); + const panelMissingType = Object.values(panels).find((panel) => panel.type === undefined); + if (panelMissingType) { + throw new Error( + `"type" attribute is missing from panel "${panelMissingType.explicitInput.id}"` + ); } - const { references: extractedReferences, state: rawExtractedState } = - deps.embeddablePersistableStateService.extract(state); - const extractedState = rawExtractedState as DashboardContainerStateWithType; - - const extractedPanels = panelStatesToPanels(extractedState.panels, panels); + const { references: extractedReferences, state: extractedState } = + deps.embeddablePersistableStateService.extract(parsedAttributes) as { + references: Reference[]; + state: ParsedDashboardAttributesWithType; + }; + const extractedPanels = convertPanelMapToSavedPanels(extractedState.panels); const newAttributes = { ...attributes, panelsJSON: JSON.stringify(extractedPanels), - } as SavedObjectAttributes; + } as DashboardAttributes; - if (extractedState.controlGroupInput) { + if (attributes.controlGroupInput && extractedState.controlGroupInput) { newAttributes.controlGroupInput = { - ...(attributes.controlGroupInput as SavedObjectAttributes), + ...attributes.controlGroupInput, panelsJSON: JSON.stringify(extractedState.controlGroupInput.panels), }; } @@ -184,14 +118,14 @@ export function extractReferences( }; } -function pre730ExtractReferences( - { attributes, references = [] }: SavedObjectAttributesAndReferences, - deps: ExtractDeps -): SavedObjectAttributesAndReferences { +function pre730ExtractReferences({ + attributes, + references = [], +}: DashboardAttributesAndReferences): DashboardAttributesAndReferences { if (typeof attributes.panelsJSON !== 'string') { return { attributes, references }; } - const panelReferences: SavedObjectReference[] = []; + const panelReferences: Reference[] = []; const panels: Array> = JSON.parse(String(attributes.panelsJSON)); panels.forEach((panel, i) => { diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index 612314da5245d..bdc692af18246 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -export type { - GridData, - DashboardOptions, - DashboardCapabilities, - SharedDashboardState, -} from './types'; +export type { DashboardOptions, DashboardCapabilities, SharedDashboardState } from './types'; export type { DashboardPanelMap, @@ -20,11 +15,7 @@ export type { DashboardContainerByReferenceInput, } from './dashboard_container/types'; -export type { - DashboardAttributes, - ParsedDashboardAttributes, - SavedDashboardPanel, -} from './dashboard_saved_object/types'; +export type { DashboardAttributes } from './content_management'; export { injectReferences, diff --git a/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts b/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts index 7683af957e837..f9da81d5d998b 100644 --- a/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts +++ b/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts @@ -10,9 +10,9 @@ import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, } from './dashboard_panel_converters'; -import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types'; -import { SavedDashboardPanel } from '../dashboard_saved_object/types'; +import { SavedDashboardPanel } from '../content_management'; import { DashboardPanelState } from '../dashboard_container/types'; +import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types'; test('convertSavedDashboardPanelToPanelState', () => { const savedDashboardPanel: SavedDashboardPanel = { @@ -46,6 +46,8 @@ test('convertSavedDashboardPanelToPanelState', () => { savedObjectId: 'savedObjectId', }, type: 'search', + panelRefName: undefined, + version: '7.0.0', }); }); diff --git a/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts index 15f991f5ac70d..117d949c6c097 100644 --- a/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts +++ b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts @@ -9,13 +9,15 @@ import { omit } from 'lodash'; import { EmbeddableInput, SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; -import { DashboardPanelMap, DashboardPanelState, SavedDashboardPanel } from '..'; +import { DashboardPanelMap, DashboardPanelState } from '..'; +import { SavedDashboardPanel } from '../content_management'; export function convertSavedDashboardPanelToPanelState< TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput >(savedDashboardPanel: SavedDashboardPanel): DashboardPanelState { return { type: savedDashboardPanel.type, + version: savedDashboardPanel.version, gridData: savedDashboardPanel.gridData, panelRefName: savedDashboardPanel.panelRefName, explicitInput: { @@ -29,11 +31,11 @@ export function convertSavedDashboardPanelToPanelState< export function convertPanelStateToSavedDashboardPanel( panelState: DashboardPanelState, - version: string + version?: string ): SavedDashboardPanel { const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; return { - version, + version: version ?? (panelState.version as string), // temporary cast. Version will be mandatory at a later date. type: panelState.type, gridData: panelState.gridData, panelIndex: panelState.explicitInput.id, @@ -52,8 +54,11 @@ export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): Da return panelsMap; }; -export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap, version: string) => { +export const convertPanelMapToSavedPanels = ( + panels: DashboardPanelMap, + versionOverride?: string +) => { return Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, version) + convertPanelStateToSavedDashboardPanel(panel, versionOverride) ); }; diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts index bf7cedbadec45..178c28b97dfa1 100644 --- a/src/plugins/dashboard/common/types.ts +++ b/src/plugins/dashboard/common/types.ts @@ -6,11 +6,12 @@ * Side Public License, v 1. */ -import { EmbeddableInput, EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; +import { Reference } from '@kbn/content-management-utils'; +import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; -import { SavedDashboardPanel } from './dashboard_saved_object/types'; -import { DashboardContainerInput, DashboardPanelState } from './dashboard_container/types'; +import { DashboardAttributes, SavedDashboardPanel } from './content_management'; +import { DashboardContainerInput, DashboardPanelMap } from './dashboard_container/types'; export interface DashboardOptions { hidePanelTitles: boolean; @@ -36,26 +37,15 @@ export type SharedDashboardState = Partial< >; /** - * Grid type for React Grid Layout + * A partially parsed version of the Dashboard Attributes used for inject and extract logic for both the Dashboard Container and the Dashboard Saved Object. */ -export interface GridData { - w: number; - h: number; - x: number; - y: number; - i: string; -} - -/** - * Types below this line are copied here because so many important types are tied up in public. These types should be - * moved from public into common. - * - * TODO replace this type with a type that uses the real Dashboard Input type. - * See https://github.com/elastic/kibana/issues/147488 for more information. - */ -export interface DashboardContainerStateWithType extends EmbeddableStateWithType { - panels: { - [panelId: string]: DashboardPanelState; - }; +export type ParsedDashboardAttributesWithType = EmbeddableStateWithType & { controlGroupInput?: PersistableControlGroupInput; + panels: DashboardPanelMap; + type: 'dashboard'; +}; + +export interface DashboardAttributesAndReferences { + attributes: DashboardAttributes; + references: Reference[]; } diff --git a/src/plugins/dashboard/kibana.jsonc b/src/plugins/dashboard/kibana.jsonc index aba26ca66ed7d..dbf2f49b4857d 100644 --- a/src/plugins/dashboard/kibana.jsonc +++ b/src/plugins/dashboard/kibana.jsonc @@ -18,6 +18,7 @@ "savedObjects", "savedObjectsFinder", "savedObjectsManagement", + "contentManagement", "share", "screenshotMode", "uiActions", @@ -34,10 +35,6 @@ "usageCollection", "taskManager" ], - "requiredBundles": [ - "kibanaReact", - "kibanaUtils", - "presentationUtil" - ] + "requiredBundles": ["kibanaReact", "kibanaUtils", "presentationUtil"] } } diff --git a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx index 228db7138fd54..cd65f3b9cff45 100644 --- a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx @@ -9,7 +9,9 @@ import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; +// TODO Remove this usage of the SavedObjectsStart contract. import { SavedObjectsStart } from '@kbn/core/public'; + import { ViewMode, PanelState, @@ -150,6 +152,7 @@ export class ClonePanelAction implements Action { embeddable: IEmbeddable, objectIdToClone: string ): Promise { + // TODO: Remove this entire functionality. See https://github.com/elastic/kibana/issues/158632 for more info. const savedObjectToClone = await this.savedObjects.client.get( embeddable.type, objectIdToClone @@ -183,6 +186,7 @@ export class ClonePanelAction implements Action { title: newTitle, hidePanelTitles: panelToClone.explicitInput.hidePanelTitles, }, + version: panelToClone.version, }; } else { panelState = { @@ -191,7 +195,10 @@ export class ClonePanelAction implements Action { ...panelToClone.explicitInput, id: uuidv4(), }, + version: panelToClone.version, }; + + // TODO Remove the entire `addCloneToLibrary` section from here. if (panelToClone.explicitInput.savedObjectId) { const clonedSavedObjectId = await this.addCloneToLibrary( embeddable, diff --git a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx index 847126a9b9427..dc4f6f07642c3 100644 --- a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx +++ b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx @@ -13,7 +13,7 @@ import { pluginServices } from '../../services/plugin_services'; import { createDashboardEditUrl } from '../../dashboard_constants'; import { getDashboardURL404String } from '../_dashboard_app_strings'; import { useDashboardMountContext } from './dashboard_mount_context'; -import { LoadDashboardFromSavedObjectReturn } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object'; +import { LoadDashboardReturn } from '../../services/dashboard_content_management/types'; export const useDashboardOutcomeValidation = ({ redirectTo, @@ -37,7 +37,7 @@ export const useDashboardOutcomeValidation = ({ } = pluginServices.getServices(); const validateOutcome = useCallback( - ({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardFromSavedObjectReturn) => { + ({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardReturn) => { if (!dashboardFound) { toasts.addDanger(getDashboardURL404String()); redirectTo({ destination: 'listing' }); @@ -45,11 +45,7 @@ export const useDashboardOutcomeValidation = ({ } if (resolveMeta && dashboardId) { - const { - outcome: loadOutcome, - alias_target_id: alias, - alias_purpose: aliasPurpose, - } = resolveMeta; + const { outcome: loadOutcome, aliasTargetId: alias, aliasPurpose } = resolveMeta; /** * Handle saved object resolve alias outcome by redirecting. */ diff --git a/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.test.tsx b/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.test.tsx index 3ae147d9e7e40..6745028642560 100644 --- a/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.test.tsx +++ b/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.test.tsx @@ -88,7 +88,7 @@ test('When given a title that matches multiple dashboards, filter on the title', props.title = title; ( - pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock + pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock ).mockResolvedValue(undefined); let component: ReactWrapper; @@ -110,7 +110,7 @@ test('When given a title that matches one dashboard, redirect to dashboard', asy const props = makeDefaultProps(); props.title = title; ( - pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock + pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock ).mockResolvedValue({ id: 'you_found_me' }); let component: ReactWrapper; diff --git a/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.tsx b/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.tsx index 9e46bb1c3cabe..019bd3a7749ab 100644 --- a/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.tsx +++ b/src/plugins/dashboard/public/dashboard_app/listing_page/dashboard_listing_page.tsx @@ -38,7 +38,7 @@ export const DashboardListingPage = ({ const { data: { query }, chrome: { setBreadcrumbs }, - dashboardSavedObject: { findDashboards }, + dashboardContentManagement: { findDashboards }, } = pluginServices.getServices(); const [showNoDataPage, setShowNoDataPage] = useState(false); diff --git a/src/plugins/dashboard/public/dashboard_app/locator/locator.ts b/src/plugins/dashboard/public/dashboard_app/locator/locator.ts index 073105f38cddf..4b8a93d8ad900 100644 --- a/src/plugins/dashboard/public/dashboard_app/locator/locator.ts +++ b/src/plugins/dashboard/public/dashboard_app/locator/locator.ts @@ -15,8 +15,9 @@ import { SerializableControlGroupInput } from '@kbn/controls-plugin/common'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import type { DashboardContainerInput } from '../../../common'; +import { SavedDashboardPanel } from '../../../common/content_management'; import { DASHBOARD_APP_ID, SEARCH_SESSION_ID } from '../../dashboard_constants'; -import type { DashboardContainerInput, SavedDashboardPanel } from '../../../common'; /** * Useful for ensuring that we don't pass any non-serializable values to history.push (for example, functions). diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx index 8ac32563d3e19..13e45678134d5 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx @@ -19,7 +19,7 @@ import { topNavStrings } from '../_dashboard_app_strings'; import { ShowShareModal } from './share/show_share_modal'; import { pluginServices } from '../../services/plugin_services'; import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants'; -import { SaveDashboardReturn } from '../../services/dashboard_saved_object/types'; +import { SaveDashboardReturn } from '../../services/dashboard_content_management/types'; import { confirmDiscardUnsavedChanges } from '../../dashboard_listing/confirm_overlays'; export const useDashboardMenuItems = ({ diff --git a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts index 8be9b1a35f71c..6d6872ae73b0f 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts @@ -15,7 +15,6 @@ import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common'; import { DashboardPanelMap, - SavedDashboardPanel, SharedDashboardState, convertSavedPanelsToPanelMap, DashboardContainerInput, @@ -24,7 +23,8 @@ import { DashboardAPI } from '../../dashboard_container'; import { pluginServices } from '../../services/plugin_services'; import { getPanelTooOldErrorString } from '../_dashboard_app_strings'; import { DASHBOARD_STATE_STORAGE_KEY } from '../../dashboard_constants'; -import { migrateLegacyQuery } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object'; +import { SavedDashboardPanel } from '../../../common/content_management'; +import { migrateLegacyQuery } from '../../services/dashboard_content_management/lib/load_dashboard_state'; /** * We no longer support loading panels from a version older than 7.3 in the URL. diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 0d8c72395f300..572d1b9d0f11a 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -53,7 +53,6 @@ export const DASHBOARD_UI_METRIC_ID = 'dashboard'; export const DASHBOARD_APP_ID = 'dashboards'; export const LEGACY_DASHBOARD_APP_ID = 'dashboard'; export const SEARCH_SESSION_ID = 'searchSessionId'; -export const DASHBOARD_SAVED_OBJECT_TYPE = 'dashboard'; // ------------------------------------------------------------------ // Grid @@ -66,6 +65,8 @@ export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; export const CHANGE_CHECK_DEBOUNCE = 100; +export { CONTENT_ID as DASHBOARD_CONTENT_ID } from '../common/content_management/constants'; + // ------------------------------------------------------------------ // Default State // ------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts index e570e1eadd6ca..829b26072f0d9 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts @@ -8,7 +8,8 @@ import _ from 'lodash'; import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; -import { DashboardPanelState, GridData } from '../../../../common'; +import { DashboardPanelState } from '../../../../common'; +import { GridData } from '../../../../common/content_management'; import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; export type PanelPlacementMethod = ( diff --git a/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx b/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx index 881f245ee7733..99592dac7aa2a 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx @@ -39,7 +39,7 @@ const DUPLICATE_TITLE_CALLOUT_ID = 'duplicateTitleCallout'; export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { const { savedObjectsTagging: { components }, - dashboardSavedObject: { checkForDuplicateDashboardTitle }, + dashboardContentManagement: { checkForDuplicateDashboardTitle }, } = pluginServices.getServices(); const dashboard = useDashboardContainer(); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index 5a8024471aed4..09eba955766c2 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -11,14 +11,14 @@ import { batch } from 'react-redux'; import { showSaveModal } from '@kbn/saved-objects-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; -import { DASHBOARD_SAVED_OBJECT_TYPE, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants'; +import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants'; import { DashboardSaveOptions, DashboardStateFromSaveModal } from '../../types'; import { DashboardSaveModal } from './overlays/save_modal'; import { DashboardContainer } from '../dashboard_container'; import { showCloneModal } from './overlays/show_clone_modal'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardContainerInput } from '../../../../common'; -import { SaveDashboardReturn } from '../../../services/dashboard_saved_object/types'; +import { SaveDashboardReturn } from '../../../services/dashboard_content_management/types'; export function runSaveAs(this: DashboardContainer) { const { @@ -28,7 +28,7 @@ export function runSaveAs(this: DashboardContainer) { }, }, savedObjectsTagging: { hasApi: hasSavedObjectsTagging }, - dashboardSavedObject: { checkForDuplicateDashboardTitle, saveDashboardStateToSavedObject }, + dashboardContentManagement: { checkForDuplicateDashboardTitle, saveDashboardState }, } = pluginServices.getServices(); const { @@ -81,7 +81,7 @@ export function runSaveAs(this: DashboardContainer) { ...stateFromSaveModal, }; const beforeAddTime = window.performance.now(); - const saveResult = await saveDashboardStateToSavedObject({ + const saveResult = await saveDashboardState({ currentState: stateToSave, saveOptions, lastSavedId, @@ -91,7 +91,7 @@ export function runSaveAs(this: DashboardContainer) { eventName: SAVED_OBJECT_POST_TIME, duration: addDuration, meta: { - saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE, + saved_object_type: DASHBOARD_CONTENT_ID, }, }); @@ -127,7 +127,7 @@ export function runSaveAs(this: DashboardContainer) { */ export async function runQuickSave(this: DashboardContainer) { const { - dashboardSavedObject: { saveDashboardStateToSavedObject }, + dashboardContentManagement: { saveDashboardState }, } = pluginServices.getServices(); const { @@ -135,7 +135,7 @@ export async function runQuickSave(this: DashboardContainer) { componentState: { lastSavedId }, } = this.getState(); - const saveResult = await saveDashboardStateToSavedObject({ + const saveResult = await saveDashboardState({ lastSavedId, currentState, saveOptions: {}, @@ -147,7 +147,7 @@ export async function runQuickSave(this: DashboardContainer) { export async function runClone(this: DashboardContainer) { const { - dashboardSavedObject: { saveDashboardStateToSavedObject, checkForDuplicateDashboardTitle }, + dashboardContentManagement: { saveDashboardState, checkForDuplicateDashboardTitle }, } = pluginServices.getServices(); const { explicitInput: currentState } = this.getState(); @@ -170,7 +170,7 @@ export async function runClone(this: DashboardContainer) { // do not clone if title is duplicate and is unconfirmed return {}; } - const saveResult = await saveDashboardStateToSavedObject({ + const saveResult = await saveDashboardState({ saveOptions: { saveAsCopy: true }, currentState: { ...currentState, title: newTitle }, }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index 4bc188c69a2c1..60c8c6cde2e75 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -52,7 +52,7 @@ test('throws error when provided validation function returns invalid', async () }); test('pulls state from dashboard saved object when given a saved object id', async () => { - pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest + pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest .fn() .mockResolvedValue({ dashboardInput: { @@ -62,13 +62,13 @@ test('pulls state from dashboard saved object when given a saved object id', asy }); const dashboard = await createDashboard({}, 0, 'wow-such-id'); expect( - pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject + pluginServices.getServices().dashboardContentManagement.loadDashboardState ).toHaveBeenCalledWith({ id: 'wow-such-id' }); expect(dashboard.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`); }); test('pulls state from session storage which overrides state from saved object', async () => { - pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest + pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest .fn() .mockResolvedValue({ dashboardInput: { @@ -86,7 +86,7 @@ test('pulls state from session storage which overrides state from saved object', }); test('pulls state from creation options initial input which overrides all other state sources', async () => { - pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest + pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest .fn() .mockResolvedValue({ dashboardInput: { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 58983196ea231..af564b5ac3c68 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -24,10 +24,10 @@ import { pluginServices } from '../../../services/plugin_services'; import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; import { DashboardCreationOptions } from '../dashboard_container_factory'; import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data_views'; +import { LoadDashboardReturn } from '../../../services/dashboard_content_management/types'; import { syncUnifiedSearchState } from './unified_search/sync_dashboard_unified_search_state'; import { startSyncingDashboardControlGroup } from './controls/dashboard_control_group_integration'; import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration'; -import { LoadDashboardFromSavedObjectReturn } from '../../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object'; /** * Builds a new Dashboard from scratch. @@ -39,7 +39,7 @@ export const createDashboard = async ( ): Promise => { const { data: { dataViews }, - dashboardSavedObject: { loadDashboardStateFromSavedObject }, + dashboardContentManagement: { loadDashboardState }, } = pluginServices.getServices(); // -------------------------------------------------------------------------------------- @@ -59,7 +59,7 @@ export const createDashboard = async ( // -------------------------------------------------------------------------------------- const reduxEmbeddablePackagePromise = lazyLoadReduxToolsPackage(); const defaultDataViewAssignmentPromise = dataViews.getDefaultDataView(); - const dashboardSavedObjectPromise = loadDashboardStateFromSavedObject({ id: savedObjectId }); + const dashboardSavedObjectPromise = loadDashboardState({ id: savedObjectId }); const [reduxEmbeddablePackage, savedObjectResult, defaultDataView] = await Promise.all([ reduxEmbeddablePackagePromise, @@ -106,7 +106,7 @@ export const initializeDashboard = async ({ creationOptions, controlGroup, }: { - loadDashboardReturn: LoadDashboardFromSavedObjectReturn; + loadDashboardReturn: LoadDashboardReturn; untilDashboardReady: () => Promise; creationOptions?: DashboardCreationOptions; controlGroup?: ControlGroupContainer; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index d6278d478796f..8954b7efb5974 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -28,7 +28,10 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public'; -import { persistableControlGroupInputIsEqual } from '@kbn/controls-plugin/common'; +import { + getDefaultControlGroupInput, + persistableControlGroupInputIsEqual, +} from '@kbn/controls-plugin/common'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; import { @@ -326,10 +329,9 @@ export class DashboardContainer extends Container(); const untilDashboardReady = () => diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx index e86ecce4d23ef..83057fdf8c934 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx @@ -25,7 +25,7 @@ import { DASHBOARD_CONTAINER_TYPE } from '..'; import type { DashboardContainer } from './dashboard_container'; import { DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants'; import { createInject, createExtract, DashboardContainerInput } from '../../../common'; -import { LoadDashboardFromSavedObjectReturn } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object'; +import { LoadDashboardReturn } from '../../services/dashboard_content_management/types'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -55,7 +55,7 @@ export interface DashboardCreationOptions { useUnifiedSearchIntegration?: boolean; unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage }; - validateLoadedSavedObject?: (result: LoadDashboardFromSavedObjectReturn) => boolean; + validateLoadedSavedObject?: (result: LoadDashboardReturn) => boolean; } export class DashboardContainerFactoryDefinition diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx index 8572356687fd6..4d867be5c1fd1 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx @@ -17,21 +17,21 @@ import { } from '@kbn/content-management-table-list'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; import { toMountPoint, useExecutionContext } from '@kbn/kibana-react-plugin/public'; -import type { SavedObjectsFindOptionsReference, SimpleSavedObject } from '@kbn/core/public'; import { + DASHBOARD_CONTENT_ID, SAVED_OBJECT_DELETE_TIME, SAVED_OBJECT_LOADED_TIME, - DASHBOARD_SAVED_OBJECT_TYPE, } from '../dashboard_constants'; import { dashboardListingTableStrings, dashboardListingErrorStrings, } from './_dashboard_listing_strings'; -import { DashboardAttributes } from '../../common'; import { pluginServices } from '../services/plugin_services'; import { confirmCreateWithUnsaved } from './confirm_overlays'; +import { DashboardItem } from '../../common/content_management'; import { DashboardUnsavedListing } from './dashboard_unsaved_listing'; import { DashboardApplicationService } from '../services/application/types'; import { DashboardListingEmptyPrompt } from './dashboard_listing_empty_prompt'; @@ -53,15 +53,13 @@ interface DashboardSavedObjectUserContent extends UserContentCommonSchema { }; } -const toTableListViewSavedObject = ( - savedObject: SimpleSavedObject -): DashboardSavedObjectUserContent => { - const { title, description, timeRestore } = savedObject.attributes; +const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUserContent => { + const { title, description, timeRestore } = hit.attributes; return { type: 'dashboard', - id: savedObject.id, - updatedAt: savedObject.updatedAt!, - references: savedObject.references, + id: hit.id, + updatedAt: hit.updatedAt!, + references: hit.references, attributes: { title, description, @@ -95,7 +93,7 @@ export const DashboardListing = ({ notifications: { toasts }, coreContext: { executionContext }, dashboardCapabilities: { showWriteControls }, - dashboardSavedObject: { findDashboards, savedObjectsClient }, + dashboardContentManagement: { findDashboards, deleteDashboards }, } = pluginServices.getServices(); const [unsavedDashboardIds, setUnsavedDashboardIds] = useState( @@ -133,8 +131,9 @@ export const DashboardListing = ({ } = {} ) => { const searchStartTime = window.performance.now(); + return findDashboards - .findSavedObjects({ + .search({ search: searchTerm, size: listingLimit, hasReference: references, @@ -147,7 +146,7 @@ export const DashboardListing = ({ eventName: SAVED_OBJECT_LOADED_TIME, duration: searchDuration, meta: { - saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE, + saved_object_type: DASHBOARD_CONTENT_ID, }, }); return { @@ -164,10 +163,10 @@ export const DashboardListing = ({ try { const deleteStartTime = window.performance.now(); - await Promise.all( + await deleteDashboards( dashboardsToDelete.map(({ id }) => { dashboardSessionStorage.clearState(id); - return savedObjectsClient.delete(DASHBOARD_SAVED_OBJECT_TYPE, id); + return id; }) ); @@ -176,7 +175,7 @@ export const DashboardListing = ({ eventName: SAVED_OBJECT_DELETE_TIME, duration: deleteDuration, meta: { - saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE, + saved_object_type: DASHBOARD_CONTENT_ID, total: dashboardsToDelete.length, }, }); @@ -188,7 +187,7 @@ export const DashboardListing = ({ setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); }, - [savedObjectsClient, dashboardSessionStorage, toasts] + [dashboardSessionStorage, deleteDashboards, toasts] ); const editItem = useCallback( diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.test.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.test.tsx index 6ae8050b0df51..edaaa21d9e08d 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.test.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.test.tsx @@ -39,7 +39,7 @@ describe('Unsaved listing', () => { mountWith({}); await waitFor(() => { expect( - pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds + pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds ).toHaveBeenCalledTimes(1); }); }); @@ -50,7 +50,7 @@ describe('Unsaved listing', () => { mountWith({ props }); await waitFor(() => { expect( - pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds + pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds ).toHaveBeenCalledWith(['dashboardUnsavedOne']); }); }); @@ -99,7 +99,7 @@ describe('Unsaved listing', () => { it('removes unsaved changes from any dashboard which errors on fetch', async () => { ( - pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds as jest.Mock + pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds as jest.Mock ).mockResolvedValue([ { id: 'failCase1', diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx index f4b7f91db77d3..ee3f4c472bc1c 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx @@ -19,9 +19,9 @@ import React, { useCallback, useEffect, useState } from 'react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { DashboardAttributes } from '../../common'; import { pluginServices } from '../services/plugin_services'; import { confirmDiscardUnsavedChanges } from './confirm_overlays'; +import { DashboardAttributes } from '../../common/content_management'; import { dashboardUnsavedListingStrings, getNewDashboardTitle } from './_dashboard_listing_strings'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_session_storage/dashboard_session_storage_service'; @@ -117,7 +117,7 @@ export const DashboardUnsavedListing = ({ }: DashboardUnsavedListingProps) => { const { dashboardSessionStorage, - dashboardSavedObject: { savedObjectsClient, findDashboards }, + dashboardContentManagement: { findDashboards }, } = pluginServices.getServices(); const [items, setItems] = useState({}); @@ -173,13 +173,7 @@ export const DashboardUnsavedListing = ({ return () => { canceled = true; }; - }, [ - refreshUnsavedDashboards, - dashboardSessionStorage, - unsavedDashboardIds, - savedObjectsClient, - findDashboards, - ]); + }, [refreshUnsavedDashboards, dashboardSessionStorage, unsavedDashboardIds, findDashboards]); return unsavedDashboardIds.length === 0 ? null : ( <> diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 093e879280d6f..41f10fd31d8bb 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -20,7 +20,6 @@ import { AppMountParameters, DEFAULT_APP_CATEGORIES, PluginInitializerContext, - SavedObjectsClientContract, } from '@kbn/core/public'; import type { ScreenshotModePluginSetup, @@ -45,6 +44,10 @@ import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/publ import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { + ContentManagementPublicSetup, + ContentManagementPublicStart, +} from '@kbn/content-management-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; @@ -64,7 +67,8 @@ import { } from './dashboard_constants'; import { DashboardMountContextProps } from './dashboard_app/types'; import { PlaceholderEmbeddableFactory } from './placeholder_embeddable'; -import type { FindDashboardsService } from './services/dashboard_saved_object/types'; +import type { FindDashboardsService } from './services/dashboard_content_management/types'; +import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; @@ -74,6 +78,7 @@ export interface DashboardSetupDependencies { data: DataPublicPluginSetup; embeddable: EmbeddableSetup; home?: HomePublicPluginSetup; + contentManagement: ContentManagementPublicSetup; screenshotMode: ScreenshotModePluginSetup; share?: SharePluginSetup; usageCollection?: UsageCollectionSetup; @@ -90,7 +95,7 @@ export interface DashboardStartDependencies { navigation: NavigationPublicPluginStart; presentationUtil: PresentationUtilPluginStart; savedObjects: SavedObjectsStart; - savedObjectsClient: SavedObjectsClientContract; + contentManagement: ContentManagementPublicStart; savedObjectsManagement: SavedObjectsManagementPluginStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; screenshotMode: ScreenshotModePluginStart; @@ -141,7 +146,7 @@ export class DashboardPlugin public setup( core: CoreSetup, - { share, embeddable, home, urlForwarding, data }: DashboardSetupDependencies + { share, embeddable, home, urlForwarding, data, contentManagement }: DashboardSetupDependencies ): DashboardSetup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get(); @@ -153,12 +158,9 @@ export class DashboardPlugin getDashboardFilterFields: async (dashboardId: string) => { const { pluginServices } = await import('./services/plugin_services'); const { - dashboardSavedObject: { loadDashboardStateFromSavedObject }, + dashboardContentManagement: { loadDashboardState }, } = pluginServices.getServices(); - return ( - (await loadDashboardStateFromSavedObject({ id: dashboardId })).dashboardInput - ?.filters ?? [] - ); + return (await loadDashboardState({ id: dashboardId })).dashboardInput?.filters ?? []; }, }) ); @@ -274,13 +276,14 @@ export class DashboardPlugin // persisted dashboard, probably with url state return `#/view/${id}${tail || ''}`; }); + const dashboardAppTitle = i18n.translate('dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }); if (home) { home.featureCatalogue.register({ id: LEGACY_DASHBOARD_APP_ID, - title: i18n.translate('dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), + title: dashboardAppTitle, subtitle: i18n.translate('dashboard.featureCatalogue.dashboardSubtitle', { defaultMessage: 'Analyze data in dashboards.', }), @@ -296,6 +299,15 @@ export class DashboardPlugin }); } + // register content management + contentManagement.registry.register({ + id: CONTENT_ID, + version: { + latest: LATEST_VERSION, + }, + name: dashboardAppTitle, + }); + return { locator: this.locator, }; @@ -317,7 +329,7 @@ export class DashboardPlugin findDashboardsService: async () => { const { pluginServices } = await import('./services/plugin_services'); const { - dashboardSavedObject: { findDashboards }, + dashboardContentManagement: { findDashboards }, } = pluginServices.getServices(); return findDashboards; }, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management.stub.ts b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management.stub.ts new file mode 100644 index 0000000000000..9bb54da53653d --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management.stub.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; + +import { DashboardContentManagementService, LoadDashboardReturn } from './types'; +import { DashboardAttributes } from '../../../common/content_management'; +import { SearchDashboardsResponse } from './lib/find_dashboards'; + +export type DashboardContentManagementServiceFactory = + PluginServiceFactory; + +export const dashboardContentManagementServiceFactory: DashboardContentManagementServiceFactory = + () => { + return { + loadDashboardState: jest.fn().mockImplementation(() => + Promise.resolve({ + dashboardInput: {}, + } as LoadDashboardReturn) + ), + saveDashboardState: jest.fn(), + findDashboards: { + search: jest.fn().mockImplementation(({ search, size }) => { + const sizeToUse = size ?? 10; + const hits: SearchDashboardsResponse['hits'] = []; + for (let i = 0; i < sizeToUse; i++) { + hits.push({ + type: 'dashboard', + id: `dashboard${i}`, + attributes: { + description: `dashboard${i} desc`, + title: `dashboard${i} - ${search} - title`, + }, + references: [] as SearchDashboardsResponse['hits'][0]['references'], + } as SearchDashboardsResponse['hits'][0]); + } + return Promise.resolve({ + total: sizeToUse, + hits, + }); + }), + findByIds: jest.fn().mockImplementation(() => + Promise.resolve([ + { + id: `dashboardUnsavedOne`, + status: 'success', + attributes: { + title: `Dashboard Unsaved One`, + } as unknown as DashboardAttributes, + }, + { + id: `dashboardUnsavedTwo`, + status: 'success', + attributes: { + title: `Dashboard Unsaved Two`, + } as unknown as DashboardAttributes, + }, + { + id: `dashboardUnsavedThree`, + status: 'success', + attributes: { + title: `Dashboard Unsaved Three`, + } as unknown as DashboardAttributes, + }, + ]) + ), + findByTitle: jest.fn(), + }, + deleteDashboards: jest.fn(), + checkForDuplicateDashboardTitle: jest.fn(), + }; + }; diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts new file mode 100644 index 0000000000000..5a5c67c798606 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts @@ -0,0 +1,83 @@ +/* + * Copyright 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 { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; + +import type { DashboardStartDependencies } from '../../plugin'; +import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashboard_title'; + +import { + searchDashboards, + findDashboardsByIds, + findDashboardIdByTitle, +} from './lib/find_dashboards'; +import { saveDashboardState } from './lib/save_dashboard_state'; +import type { + DashboardContentManagementRequiredServices, + DashboardContentManagementService, +} from './types'; +import { loadDashboardState } from './lib/load_dashboard_state'; +import { deleteDashboards } from './lib/delete_dashboards'; + +export type DashboardContentManagementServiceFactory = KibanaPluginServiceFactory< + DashboardContentManagementService, + DashboardStartDependencies, + DashboardContentManagementRequiredServices +>; + +export const dashboardContentManagementServiceFactory: DashboardContentManagementServiceFactory = ( + { startPlugins: { contentManagement } }, + requiredServices +) => { + const { + data, + embeddable, + notifications, + initializerContext, + savedObjectsTagging, + dashboardSessionStorage, + } = requiredServices; + return { + loadDashboardState: ({ id }) => + loadDashboardState({ + id, + data, + embeddable, + contentManagement, + savedObjectsTagging, + }), + saveDashboardState: ({ currentState, saveOptions, lastSavedId }) => + saveDashboardState({ + data, + embeddable, + saveOptions, + lastSavedId, + currentState, + notifications, + contentManagement, + initializerContext, + savedObjectsTagging, + dashboardSessionStorage, + }), + findDashboards: { + search: ({ hasReference, hasNoReference, search, size }) => + searchDashboards({ + contentManagement, + hasNoReference, + hasReference, + search, + size, + }), + findByIds: (ids) => findDashboardsByIds(contentManagement, ids), + findByTitle: (title) => findDashboardIdByTitle(contentManagement, title), + }, + checkForDuplicateDashboardTitle: (props) => + checkForDuplicateDashboardTitle(props, contentManagement), + deleteDashboards: (ids) => deleteDashboards(ids, contentManagement), + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts similarity index 68% rename from src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts rename to src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts index 2f106a2e1a00d..67fad986a1641 100644 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/check_for_duplicate_dashboard_title.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import type { SavedObjectsClientContract } from '@kbn/core/public'; - -import { DashboardAttributes } from '../../../../common'; -import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants'; +import { DashboardStartDependencies } from '../../../plugin'; +import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; +import { DashboardCrudTypes } from '../../../../common/content_management'; export interface DashboardDuplicateTitleCheckProps { title: string; @@ -32,7 +31,7 @@ export async function checkForDuplicateDashboardTitle( onTitleDuplicate, isTitleDuplicateConfirmed, }: DashboardDuplicateTitleCheckProps, - savedObjectsClient: SavedObjectsClientContract + contentManagement: DashboardStartDependencies['contentManagement'] ): Promise { // Don't check for duplicates if user has already confirmed save with duplicate title if (isTitleDuplicateConfirmed) { @@ -44,16 +43,19 @@ export async function checkForDuplicateDashboardTitle( if (title === lastSavedTitle && !copyOnSave) { return true; } - const response = await savedObjectsClient.find({ - perPage: 10, - fields: ['title'], - search: `"${title}"`, - searchFields: ['title'], - type: DASHBOARD_SAVED_OBJECT_TYPE, + + const { hits } = await contentManagement.client.search< + DashboardCrudTypes['SearchIn'], + DashboardCrudTypes['SearchOut'] + >({ + contentTypeId: DASHBOARD_CONTENT_ID, + query: { + text: title ? `${title}*` : undefined, + limit: 10, + }, + options: { onlyTitle: true }, }); - const duplicate = response.savedObjects.find( - (obj) => obj.get('title').toLowerCase() === title.toLowerCase() - ); + const duplicate = hits.find((hit) => hit.attributes.title.toLowerCase() === title.toLowerCase()); if (!duplicate) { return true; } diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/delete_dashboards.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/delete_dashboards.ts new file mode 100644 index 0000000000000..e18841eacfcfd --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/delete_dashboards.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardStartDependencies } from '../../../plugin'; +import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; +import { DashboardCrudTypes } from '../../../../common/content_management'; + +export const deleteDashboards = async ( + ids: string[], + contentManagement: DashboardStartDependencies['contentManagement'] +) => { + const deletePromises = ids.map((id) => + contentManagement.client.delete< + DashboardCrudTypes['DeleteIn'], + DashboardCrudTypes['DeleteOut'] + >({ + contentTypeId: DASHBOARD_CONTENT_ID, + id, + }) + ); + + await Promise.all(deletePromises); +}; diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts new file mode 100644 index 0000000000000..41bdd60d6c1d7 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { SavedObjectError, SavedObjectsFindOptionsReference } from '@kbn/core/public'; + +import { + DashboardItem, + DashboardCrudTypes, + DashboardAttributes, +} from '../../../../common/content_management'; +import { DashboardStartDependencies } from '../../../plugin'; +import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; + +export interface SearchDashboardsArgs { + contentManagement: DashboardStartDependencies['contentManagement']; + hasNoReference?: SavedObjectsFindOptionsReference[]; + hasReference?: SavedObjectsFindOptionsReference[]; + search: string; + size: number; +} + +export interface SearchDashboardsResponse { + total: number; + hits: DashboardItem[]; +} + +export async function searchDashboards({ + contentManagement, + hasNoReference, + hasReference, + search, + size, +}: SearchDashboardsArgs): Promise { + const { + hits, + pagination: { total }, + } = await contentManagement.client.search< + DashboardCrudTypes['SearchIn'], + DashboardCrudTypes['SearchOut'] + >({ + contentTypeId: DASHBOARD_CONTENT_ID, + query: { + text: search ? `${search}*` : undefined, + limit: size, + tags: { + included: (hasReference ?? []).map(({ id }) => id), + excluded: (hasNoReference ?? []).map(({ id }) => id), + }, + }, + }); + return { + total, + hits, + }; +} + +export type FindDashboardsByIdResponse = { id: string } & ( + | { status: 'success'; attributes: DashboardAttributes } + | { status: 'error'; error: SavedObjectError } +); + +export async function findDashboardsByIds( + contentManagement: DashboardStartDependencies['contentManagement'], + ids: string[] +): Promise { + const findPromises = ids.map((id) => + contentManagement.client.get({ + contentTypeId: DASHBOARD_CONTENT_ID, + id, + }) + ); + const results = await Promise.all(findPromises); + + return results.map((result) => { + if (result.item.error) return { status: 'error', error: result.item.error, id: result.item.id }; + const { attributes, id } = result.item; + return { id, status: 'success', attributes }; + }); +} + +export async function findDashboardIdByTitle( + contentManagement: DashboardStartDependencies['contentManagement'], + title: string +): Promise<{ id: string } | undefined> { + const { hits } = await contentManagement.client.search< + DashboardCrudTypes['SearchIn'], + DashboardCrudTypes['SearchOut'] + >({ + contentTypeId: DASHBOARD_CONTENT_ID, + query: { + text: title ? `${title}*` : undefined, + limit: 10, + }, + options: { onlyTitle: true }, + }); + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = hits.filter( + (hit) => hit.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + return { id: matchingDashboards[0].id }; + } +} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts similarity index 75% rename from src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts rename to src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts index 57e5ea2a21836..2942270f86195 100644 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts @@ -8,11 +8,6 @@ import { v4 as uuidv4 } from 'uuid'; import { has } from 'lodash'; -import { - ResolvedSimpleSavedObject, - SavedObjectAttributes, - SavedObjectsClientContract, -} from '@kbn/core/public'; import { Filter, Query } from '@kbn/es-query'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { cleanFiltersForSerialize } from '@kbn/presentation-util-plugin/public'; @@ -20,14 +15,13 @@ import { rawControlGroupAttributesToControlGroupInput } from '@kbn/controls-plug import { parseSearchSourceJSON, injectSearchSourceReferences } from '@kbn/data-plugin/public'; import { - convertSavedPanelsToPanelMap, - DashboardContainerInput, - DashboardAttributes, - DashboardOptions, injectReferences, + type DashboardOptions, + convertSavedPanelsToPanelMap, } from '../../../../common'; -import { DashboardSavedObjectRequiredServices } from '../types'; -import { DASHBOARD_SAVED_OBJECT_TYPE, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; +import { DashboardCrudTypes } from '../../../../common/content_management'; +import type { LoadDashboardFromSavedObjectProps, LoadDashboardReturn } from '../types'; +import { DASHBOARD_CONTENT_ID, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query { // Lucene was the only option before, so language-less queries are all lucene @@ -38,25 +32,13 @@ export function migrateLegacyQuery(query: Query | { [key: string]: any } | strin return query as Query; } -export type LoadDashboardFromSavedObjectProps = DashboardSavedObjectRequiredServices & { - id?: string; - savedObjectsClient: SavedObjectsClientContract; -}; - -export interface LoadDashboardFromSavedObjectReturn { - dashboardFound: boolean; - dashboardId?: string; - resolveMeta?: Omit; - dashboardInput: DashboardContainerInput; -} - -export const loadDashboardStateFromSavedObject = async ({ - savedObjectsTagging, - savedObjectsClient, - embeddable, - data, +export const loadDashboardState = async ({ id, -}: LoadDashboardFromSavedObjectProps): Promise => { + data, + embeddable, + contentManagement, + savedObjectsTagging, +}: LoadDashboardFromSavedObjectProps): Promise => { const { search: dataSearchService, query: { queryString }, @@ -73,29 +55,31 @@ export const loadDashboardStateFromSavedObject = async ({ if (!savedObjectId) return { dashboardInput: newDashboardState, dashboardFound: true }; /** - * Load the saved object + * Load the saved object from Content Management */ - const { saved_object: rawDashboardSavedObject, ...resolveMeta } = - await savedObjectsClient.resolve( - DASHBOARD_SAVED_OBJECT_TYPE, - savedObjectId - ); - if (!rawDashboardSavedObject._version) { - return { dashboardInput: newDashboardState, dashboardFound: false, dashboardId: savedObjectId }; + const { item: rawDashboardContent, meta: resolveMeta } = await contentManagement.client.get< + DashboardCrudTypes['GetIn'], + DashboardCrudTypes['GetOut'] + >({ contentTypeId: DASHBOARD_CONTENT_ID, id }); + if (!rawDashboardContent.version) { + return { + dashboardInput: newDashboardState, + dashboardFound: false, + dashboardId: savedObjectId, + }; } - /** * Inject saved object references back into the saved object attributes */ - const { references, attributes: rawAttributes } = rawDashboardSavedObject; + const { references, attributes: rawAttributes } = rawDashboardContent; const attributes = (() => { if (!references || references.length === 0) return rawAttributes; return injectReferences( - { references, attributes: rawAttributes as unknown as SavedObjectAttributes }, + { references, attributes: rawAttributes }, { embeddablePersistableStateService: embeddable, } - ) as unknown as DashboardAttributes; + ); })(); /** diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts similarity index 78% rename from src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts rename to src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts index d19799c02bff5..60d1a0f8972e0 100644 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts @@ -15,30 +15,23 @@ import { controlGroupInputToRawControlGroupAttributes, } from '@kbn/controls-plugin/common'; import { isFilterPinned } from '@kbn/es-query'; -import { SavedObjectsClientContract } from '@kbn/core/public'; -import { SavedObjectAttributes } from '@kbn/core-saved-objects-common'; -import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; import { extractSearchSourceReferences, RefreshInterval } from '@kbn/data-plugin/public'; import { extractReferences, - DashboardAttributes, - convertPanelMapToSavedPanels, DashboardContainerInput, + convertPanelMapToSavedPanels, } from '../../../../common'; -import { DashboardSavedObjectRequiredServices } from '../types'; -import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants'; +import { + SaveDashboardProps, + SaveDashboardReturn, + DashboardContentManagementRequiredServices, +} from '../types'; +import { DashboardStartDependencies } from '../../../plugin'; +import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; +import { DashboardCrudTypes, DashboardAttributes } from '../../../../common/content_management'; import { dashboardSaveToastStrings } from '../../../dashboard_container/_dashboard_container_strings'; -export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolean }; - -export type SaveDashboardProps = DashboardSavedObjectRequiredServices & { - savedObjectsClient: SavedObjectsClientContract; - currentState: DashboardContainerInput; - saveOptions: SavedDashboardSaveOpts; - lastSavedId?: string; -}; - export const serializeControlGroupInput = ( controlGroupInput: DashboardContainerInput['controlGroupInput'] ) => { @@ -62,24 +55,28 @@ export const convertTimeToUTCString = (time?: string | Moment): undefined | stri } }; -export interface SaveDashboardReturn { - id?: string; - error?: string; - redirectRequired?: boolean; -} +type SaveDashboardStateProps = SaveDashboardProps & { + data: DashboardContentManagementRequiredServices['data']; + contentManagement: DashboardStartDependencies['contentManagement']; + embeddable: DashboardContentManagementRequiredServices['embeddable']; + notifications: DashboardContentManagementRequiredServices['notifications']; + initializerContext: DashboardContentManagementRequiredServices['initializerContext']; + savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging']; + dashboardSessionStorage: DashboardContentManagementRequiredServices['dashboardSessionStorage']; +}; -export const saveDashboardStateToSavedObject = async ({ +export const saveDashboardState = async ({ data, embeddable, lastSavedId, saveOptions, currentState, - savedObjectsClient, + contentManagement, savedObjectsTagging, dashboardSessionStorage, notifications: { toasts }, initializerContext: { kibanaVersion }, -}: SaveDashboardProps): Promise => { +}: SaveDashboardStateProps): Promise => { const { search: dataSearchService, query: { @@ -167,7 +164,7 @@ export const saveDashboardStateToSavedObject = async ({ */ const { attributes, references: dashboardReferences } = extractReferences( { - attributes: rawDashboardAttributes as unknown as SavedObjectAttributes, + attributes: rawDashboardAttributes, references: searchSourceReferences, }, { embeddablePersistableStateService: embeddable } @@ -177,15 +174,19 @@ export const saveDashboardStateToSavedObject = async ({ : dashboardReferences; /** - * Save the saved object using the saved objects client + * Save the saved object using the content management */ const idToSaveTo = saveOptions.saveAsCopy ? undefined : lastSavedId; try { - const { id: newId } = await savedObjectsClient.create(DASHBOARD_SAVED_OBJECT_TYPE, attributes, { - id: idToSaveTo, - overwrite: true, - references, + const result = await contentManagement.client.create< + DashboardCrudTypes['CreateIn'], + DashboardCrudTypes['CreateOut'] + >({ + contentTypeId: DASHBOARD_CONTENT_ID, + data: attributes, + options: { id: idToSaveTo, references, overwrite: true }, }); + const newId = result.item.id; if (newId) { toasts.addSuccess({ diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/types.ts b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts new file mode 100644 index 0000000000000..92fbe3005e9e4 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; + +import { + FindDashboardsByIdResponse, + SearchDashboardsArgs, + SearchDashboardsResponse, +} from './lib/find_dashboards'; +import { DashboardDataService } from '../data/types'; +import { DashboardSpacesService } from '../spaces/types'; +import { DashboardContainerInput } from '../../../common'; +import { DashboardStartDependencies } from '../../plugin'; +import { DashboardEmbeddableService } from '../embeddable/types'; +import { DashboardNotificationsService } from '../notifications/types'; +import { DashboardCrudTypes } from '../../../common/content_management'; +import { DashboardScreenshotModeService } from '../screenshot_mode/types'; +import { DashboardInitializerContextService } from '../initializer_context/types'; +import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types'; +import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types'; +import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title'; + +export interface DashboardContentManagementRequiredServices { + data: DashboardDataService; + spaces: DashboardSpacesService; + embeddable: DashboardEmbeddableService; + notifications: DashboardNotificationsService; + screenshotMode: DashboardScreenshotModeService; + initializerContext: DashboardInitializerContextService; + savedObjectsTagging: DashboardSavedObjectsTaggingService; + dashboardSessionStorage: DashboardSessionStorageServiceType; +} + +export interface DashboardContentManagementService { + findDashboards: FindDashboardsService; + deleteDashboards: (ids: string[]) => void; + loadDashboardState: (props: { id?: string }) => Promise; + saveDashboardState: (props: SaveDashboardProps) => Promise; + checkForDuplicateDashboardTitle: (meta: DashboardDuplicateTitleCheckProps) => Promise; +} + +/** + * Types for Loading Dashboards + */ +export interface LoadDashboardFromSavedObjectProps { + id?: string; + data: DashboardContentManagementRequiredServices['data']; + contentManagement: DashboardStartDependencies['contentManagement']; + embeddable: DashboardContentManagementRequiredServices['embeddable']; + savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging']; +} + +type DashboardResolveMeta = DashboardCrudTypes['GetOut']['meta']; + +export interface LoadDashboardReturn { + dashboardFound: boolean; + dashboardId?: string; + resolveMeta?: DashboardResolveMeta; + dashboardInput: DashboardContainerInput; +} + +/** + * Types for Saving Dashboards + */ +export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolean }; + +export interface SaveDashboardProps { + currentState: DashboardContainerInput; + saveOptions: SavedDashboardSaveOpts; + lastSavedId?: string; +} + +export interface SaveDashboardReturn { + id?: string; + error?: string; + redirectRequired?: boolean; +} + +/** + * Types for Finding Dashboards + */ +export interface FindDashboardsService { + search: ( + props: Pick + ) => Promise; + findByIds: (ids: string[]) => Promise; + findByTitle: (title: string) => Promise<{ id: string } | undefined>; +} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts deleted file mode 100644 index 4e9fbdb31dca4..0000000000000 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object.stub.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { savedObjectsServiceMock } from '@kbn/core/public/mocks'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { FindDashboardSavedObjectsResponse } from './lib/find_dashboard_saved_objects'; - -import { DashboardSavedObjectService } from './types'; -import { DashboardAttributes } from '../../../common'; -import { LoadDashboardFromSavedObjectReturn } from './lib/load_dashboard_state_from_saved_object'; - -type DashboardSavedObjectServiceFactory = PluginServiceFactory; - -export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = () => { - const { client: savedObjectsClient } = savedObjectsServiceMock.createStartContract(); - return { - loadDashboardStateFromSavedObject: jest.fn().mockImplementation(() => - Promise.resolve({ - dashboardInput: {}, - } as LoadDashboardFromSavedObjectReturn) - ), - saveDashboardStateToSavedObject: jest.fn(), - findDashboards: { - findSavedObjects: jest.fn().mockImplementation(({ search, size }) => { - const sizeToUse = size ?? 10; - const hits: FindDashboardSavedObjectsResponse['hits'] = []; - for (let i = 0; i < sizeToUse; i++) { - hits.push({ - type: 'dashboard', - id: `dashboard${i}`, - attributes: { - description: `dashboard${i} desc`, - title: `dashboard${i} - ${search} - title`, - }, - references: [] as FindDashboardSavedObjectsResponse['hits'][0]['references'], - } as FindDashboardSavedObjectsResponse['hits'][0]); - } - return Promise.resolve({ - total: sizeToUse, - hits, - }); - }), - findByIds: jest.fn().mockImplementation(() => - Promise.resolve([ - { - id: `dashboardUnsavedOne`, - status: 'success', - attributes: { - title: `Dashboard Unsaved One`, - } as unknown as DashboardAttributes, - }, - { - id: `dashboardUnsavedTwo`, - status: 'success', - attributes: { - title: `Dashboard Unsaved Two`, - } as unknown as DashboardAttributes, - }, - { - id: `dashboardUnsavedThree`, - status: 'success', - attributes: { - title: `Dashboard Unsaved Three`, - } as unknown as DashboardAttributes, - }, - ]) - ), - findByTitle: jest.fn(), - }, - checkForDuplicateDashboardTitle: jest.fn(), - savedObjectsClient, - }; -}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts deleted file mode 100644 index 658fda8ddb461..0000000000000 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/dashboard_saved_object_service.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; - -import type { DashboardStartDependencies } from '../../plugin'; -import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashboard_title'; -import { - findDashboardIdByTitle, - findDashboardSavedObjects, - findDashboardSavedObjectsByIds, -} from './lib/find_dashboard_saved_objects'; -import { saveDashboardStateToSavedObject } from './lib/save_dashboard_state_to_saved_object'; -import { loadDashboardStateFromSavedObject } from './lib/load_dashboard_state_from_saved_object'; -import type { DashboardSavedObjectRequiredServices, DashboardSavedObjectService } from './types'; - -export type DashboardSavedObjectServiceFactory = KibanaPluginServiceFactory< - DashboardSavedObjectService, - DashboardStartDependencies, - DashboardSavedObjectRequiredServices ->; - -export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = ( - { coreStart }, - requiredServices -) => { - const { - savedObjects: { client: savedObjectsClient }, - } = coreStart; - - return { - loadDashboardStateFromSavedObject: ({ id }) => - loadDashboardStateFromSavedObject({ - id, - savedObjectsClient, - ...requiredServices, - }), - saveDashboardStateToSavedObject: ({ currentState, saveOptions, lastSavedId }) => - saveDashboardStateToSavedObject({ - saveOptions, - lastSavedId, - currentState, - savedObjectsClient, - ...requiredServices, - }), - findDashboards: { - findSavedObjects: ({ hasReference, hasNoReference, search, size }) => - findDashboardSavedObjects({ - hasReference, - hasNoReference, - search, - size, - savedObjectsClient, - }), - findByIds: (ids) => findDashboardSavedObjectsByIds(savedObjectsClient, ids), - findByTitle: (title) => findDashboardIdByTitle(title, savedObjectsClient), - }, - checkForDuplicateDashboardTitle: (props) => - checkForDuplicateDashboardTitle(props, savedObjectsClient), - savedObjectsClient, - }; -}; diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts deleted file mode 100644 index 36f003119d6ac..0000000000000 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/lib/find_dashboard_saved_objects.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - SavedObjectError, - SavedObjectsClientContract, - SavedObjectsFindOptionsReference, - SimpleSavedObject, -} from '@kbn/core/public'; - -import { DashboardAttributes } from '../../../../common'; -import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants'; - -export interface FindDashboardSavedObjectsArgs { - hasReference?: SavedObjectsFindOptionsReference[]; - hasNoReference?: SavedObjectsFindOptionsReference[]; - savedObjectsClient: SavedObjectsClientContract; - search: string; - size: number; -} - -export interface FindDashboardSavedObjectsResponse { - total: number; - hits: Array>; -} - -export async function findDashboardSavedObjects({ - savedObjectsClient, - hasReference, - hasNoReference, - search, - size, -}: FindDashboardSavedObjectsArgs): Promise { - const { total, savedObjects } = await savedObjectsClient.find({ - type: DASHBOARD_SAVED_OBJECT_TYPE, - search: search ? `${search}*` : undefined, - searchFields: ['title^3', 'description'], - defaultSearchOperator: 'AND' as 'AND', - perPage: size, - hasReference, - hasNoReference, - page: 1, - }); - return { - total, - hits: savedObjects, - }; -} - -export type FindDashboardBySavedObjectIdsResult = { id: string } & ( - | { status: 'success'; attributes: DashboardAttributes } - | { status: 'error'; error: SavedObjectError } -); - -export async function findDashboardSavedObjectsByIds( - savedObjectsClient: SavedObjectsClientContract, - ids: string[] -): Promise { - const { savedObjects } = await savedObjectsClient.bulkGet( - ids.map((id) => ({ id, type: DASHBOARD_SAVED_OBJECT_TYPE })) - ); - - return savedObjects.map((savedObjectResult) => { - if (savedObjectResult.error) - return { status: 'error', error: savedObjectResult.error, id: savedObjectResult.id }; - const { attributes, id } = savedObjectResult; - return { - id, - status: 'success', - attributes: attributes as DashboardAttributes, - }; - }); -} - -export async function findDashboardIdByTitle( - title: string, - savedObjectsClient: SavedObjectsClientContract -): Promise<{ id: string } | undefined> { - const results = await savedObjectsClient.find({ - search: `"${title}"`, - searchFields: ['title'], - type: 'dashboard', - }); - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - (dashboard) => dashboard.attributes.title.toLowerCase() === title.toLowerCase() - ); - if (matchingDashboards.length === 1) { - return { id: matchingDashboards[0].id }; - } -} diff --git a/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts b/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts deleted file mode 100644 index fc9b2f4df5433..0000000000000 --- a/src/plugins/dashboard/public/services/dashboard_saved_object/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsClientContract } from '@kbn/core/public'; - -import { DashboardDataService } from '../data/types'; -import { DashboardSpacesService } from '../spaces/types'; -import { DashboardEmbeddableService } from '../embeddable/types'; -import { DashboardNotificationsService } from '../notifications/types'; -import { DashboardScreenshotModeService } from '../screenshot_mode/types'; -import { DashboardInitializerContextService } from '../initializer_context/types'; -import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types'; -import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types'; - -import { - LoadDashboardFromSavedObjectProps, - LoadDashboardFromSavedObjectReturn, -} from './lib/load_dashboard_state_from_saved_object'; -import { - SaveDashboardProps, - SaveDashboardReturn, -} from './lib/save_dashboard_state_to_saved_object'; -import { - FindDashboardBySavedObjectIdsResult, - FindDashboardSavedObjectsArgs, - FindDashboardSavedObjectsResponse, -} from './lib/find_dashboard_saved_objects'; -import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title'; - -export interface DashboardSavedObjectRequiredServices { - screenshotMode: DashboardScreenshotModeService; - embeddable: DashboardEmbeddableService; - spaces: DashboardSpacesService; - data: DashboardDataService; - initializerContext: DashboardInitializerContextService; - notifications: DashboardNotificationsService; - savedObjectsTagging: DashboardSavedObjectsTaggingService; - dashboardSessionStorage: DashboardSessionStorageServiceType; -} - -export interface FindDashboardsService { - findSavedObjects: ( - props: Pick< - FindDashboardSavedObjectsArgs, - 'hasReference' | 'hasNoReference' | 'search' | 'size' - > - ) => Promise; - findByIds: (ids: string[]) => Promise; - findByTitle: (title: string) => Promise<{ id: string } | undefined>; -} - -export interface DashboardSavedObjectService { - loadDashboardStateFromSavedObject: ( - props: Pick - ) => Promise; - - saveDashboardStateToSavedObject: ( - props: Pick - ) => Promise; - findDashboards: FindDashboardsService; - checkForDuplicateDashboardTitle: (meta: DashboardDuplicateTitleCheckProps) => Promise; - savedObjectsClient: SavedObjectsClientContract; -} - -export type { SaveDashboardReturn }; diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts index f0ec4a91f3fd6..e54cb783c71d6 100644 --- a/src/plugins/dashboard/public/services/plugin_services.stub.ts +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -37,12 +37,12 @@ import { usageCollectionServiceFactory } from './usage_collection/usage_collecti import { spacesServiceFactory } from './spaces/spaces.stub'; import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub'; import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; -import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub'; +import { dashboardContentManagementServiceFactory } from './dashboard_content_management/dashboard_content_management.stub'; import { customBrandingServiceFactory } from './custom_branding/custom_branding.stub'; import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service.stub'; export const providers: PluginServiceProviders = { - dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory), + dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory), analytics: new PluginServiceProvider(analyticsServiceFactory), application: new PluginServiceProvider(applicationServiceFactory), chrome: new PluginServiceProvider(chromeServiceFactory), diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts index 599eb6a5b7249..716010cdbf0a8 100644 --- a/src/plugins/dashboard/public/services/plugin_services.ts +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -38,12 +38,12 @@ import { urlForwardingServiceFactory } from './url_forwarding/url_forwarding_ser import { visualizationsServiceFactory } from './visualizations/visualizations_service'; import { usageCollectionServiceFactory } from './usage_collection/usage_collection_service'; import { analyticsServiceFactory } from './analytics/analytics_service'; -import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service'; import { customBrandingServiceFactory } from './custom_branding/custom_branding_service'; import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service'; +import { dashboardContentManagementServiceFactory } from './dashboard_content_management/dashboard_content_management_service'; const providers: PluginServiceProviders = { - dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [ + dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory, [ 'dashboardSessionStorage', 'savedObjectsTagging', 'initializerContext', diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts index 1992cd83d4b6d..2bb3753b132f0 100644 --- a/src/plugins/dashboard/public/services/types.ts +++ b/src/plugins/dashboard/public/services/types.ts @@ -17,7 +17,7 @@ import { DashboardChromeService } from './chrome/types'; import { DashboardCoreContextService } from './core_context/types'; import { DashboardCustomBrandingService } from './custom_branding/types'; import { DashboardCapabilitiesService } from './dashboard_capabilities/types'; -import { DashboardSavedObjectService } from './dashboard_saved_object/types'; +import { DashboardContentManagementService } from './dashboard_content_management/types'; import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types'; import { DashboardDataService } from './data/types'; import { DashboardDataViewEditorService } from './data_view_editor/types'; @@ -41,8 +41,8 @@ export type DashboardPluginServiceParams = KibanaPluginServiceParams { + const { query, contentTypeId, options } = args; + + return { + type: contentTypeId, + searchFields: options?.onlyTitle ? ['title'] : ['title^3', 'description'], + fields: ['description', 'title', 'timeRestore'], + search: query.text, + perPage: query.limit, + page: query.cursor ? +query.cursor : undefined, + defaultSearchOperator: 'AND', + ...tagsToFindOptions(query.tags), + }; +}; + +export class DashboardStorage extends SOContentStorage { + constructor() { + super({ + savedObjectType: CONTENT_ID, + cmServicesDefinition, + searchArgsToSOFindOptions, + enableMSearch: true, + allowedSavedObjectAttributes: [ + 'kibanaSavedObjectMeta', + 'controlGroupInput', + 'refreshInterval', + 'description', + 'timeRestore', + 'optionsJSON', + 'panelsJSON', + 'timeFrom', + 'timeTo', + 'title', + ], + }); + } +} diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/index.ts b/src/plugins/dashboard/server/content_management/index.ts similarity index 70% rename from packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/index.ts rename to src/plugins/dashboard/server/content_management/index.ts index 8f22bad9f10f6..6c5db492a0ea2 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/index.ts +++ b/src/plugins/dashboard/server/content_management/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export { SavedObjectsImporter, SavedObjectsImportError } from './import'; -export { SavedObjectsExporter, SavedObjectsExportError } from './export'; +export { DashboardStorage } from './dashboard_storage'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts index 9844d565ee36f..31595cb6268bb 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { schema } from '@kbn/config-schema'; import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { @@ -69,5 +70,46 @@ export const createDashboardSavedObjectType = ({ version: { type: 'integer' }, }, }, + schemas: { + '8.9.0': schema.object({ + // General + title: schema.string(), + description: schema.string({ defaultValue: '' }), + + // Search + kibanaSavedObjectMeta: schema.object({ + searchSourceJSON: schema.maybe(schema.string()), + }), + + // Time + timeRestore: schema.maybe(schema.boolean()), + timeFrom: schema.maybe(schema.string()), + timeTo: schema.maybe(schema.string()), + refreshInterval: schema.maybe( + schema.object({ + pause: schema.boolean(), + value: schema.number(), + display: schema.maybe(schema.string()), + section: schema.maybe(schema.number()), + }) + ), + + // Dashboard Content + controlGroupInput: schema.maybe( + schema.object({ + panelsJSON: schema.maybe(schema.string()), + controlStyle: schema.maybe(schema.string()), + chainingSystem: schema.maybe(schema.string()), + ignoreParentSettingsJSON: schema.maybe(schema.string()), + }) + ), + panelsJSON: schema.string({ defaultValue: '[]' }), + optionsJSON: schema.string({ defaultValue: '{}' }), + + // Legacy + hits: schema.maybe(schema.number()), + version: schema.maybe(schema.number()), + }), + }, migrations: () => createDashboardSavedObjectTypeMigrations(migrationDeps), }); diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts index 3bad12b537103..1b671112e1b1e 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts @@ -19,8 +19,8 @@ import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; import { convertPanelStateToSavedDashboardPanel, convertSavedDashboardPanelToPanelState, - SavedDashboardPanel, } from '../../../common'; +import { SavedDashboardPanel } from '../../../common/content_management'; type ValueOrReferenceInput = SavedObjectEmbeddableInput & { attributes?: Serializable; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts index 5a8de73af988b..068999358918b 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { SavedObjectAttributes, SavedObjectMigrationFn } from '@kbn/core/server'; +import { SavedObjectMigrationFn } from '@kbn/core/server'; -import { DashboardAttributes, extractReferences, injectReferences } from '../../../common'; +import { extractReferences, injectReferences } from '../../../common'; +import { DashboardAttributes } from '../../../common/content_management'; import { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations'; /** @@ -36,7 +37,7 @@ export function createExtractPanelReferencesMigration( const injectedAttributes = injectReferences( { - attributes: doc.attributes as unknown as SavedObjectAttributes, + attributes: doc.attributes, references, }, { embeddablePersistableStateService: deps.embeddable } diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts index 8a9a917231204..d071a094ac19c 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts @@ -12,8 +12,8 @@ import { EmbeddableInput } from '@kbn/embeddable-plugin/common'; import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, - SavedDashboardPanel, } from '../../../common'; +import { SavedDashboardPanel } from '../../../common/content_management'; /** * Before 7.10, hidden panel titles were stored as a blank string on the title attribute. In 7.10, this was replaced diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts index 0052604e19f66..d0a7b81bc5c8d 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts @@ -24,7 +24,7 @@ import { RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, } from './types'; -import { GridData } from '../../../../common'; +import { GridData } from '../../../../common/content_management'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts index 2257b05c0a64e..c6994a6f6807c 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts @@ -12,28 +12,28 @@ import { SavedObjectReference } from '@kbn/core/server'; import type { GridData, DashboardAttributes as CurrentDashboardAttributes, // Dashboard attributes from common are the source of truth for the current version. -} from '../../../../common'; +} from '../../../../common/content_management'; -interface SavedObjectAttributes { +interface KibanaAttributes { kibanaSavedObjectMeta: { searchSourceJSON: string; }; } -interface Doc { +interface Doc { references: SavedObjectReference[]; attributes: Attributes; id: string; type: string; } -interface DocPre700 { +interface DocPre700 { attributes: Attributes; id: string; type: string; } -interface DashboardAttributesTo720 extends SavedObjectAttributes { +interface DashboardAttributesTo720 extends KibanaAttributes { panelsJSON: string; description: string; uiStateJSON?: string; diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts index afbf198c79b9e..8dbc074fefa59 100644 --- a/src/plugins/dashboard/server/index.ts +++ b/src/plugins/dashboard/server/index.ts @@ -25,4 +25,3 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { DashboardPluginSetup, DashboardPluginStart } from './types'; -export { findByValueEmbeddables } from './usage/find_by_value_embeddables'; diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts index 404b3ee998cc7..8a68d406d16c9 100644 --- a/src/plugins/dashboard/server/plugin.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -6,32 +6,34 @@ * Side Public License, v 1. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; - -import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { createDashboardSavedObjectType } from './dashboard_saved_object'; -import { capabilitiesProvider } from './capabilities_provider'; - -import { DashboardPluginSetup, DashboardPluginStart } from './types'; -import { registerDashboardUsageCollector } from './usage/register_collector'; -import { dashboardPersistableStateServiceFactory } from './dashboard_container/dashboard_container_embeddable_factory'; -import { getUISettings } from './ui_settings'; +import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; import { initializeDashboardTelemetryTask, scheduleDashboardTelemetry, TASK_ID, } from './usage/dashboard_telemetry_collection_task'; +import { getUISettings } from './ui_settings'; +import { DashboardStorage } from './content_management'; +import { capabilitiesProvider } from './capabilities_provider'; +import { DashboardPluginSetup, DashboardPluginStart } from './types'; +import { createDashboardSavedObjectType } from './dashboard_saved_object'; +import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; +import { registerDashboardUsageCollector } from './usage/register_collector'; +import { dashboardPersistableStateServiceFactory } from './dashboard_container/dashboard_container_embeddable_factory'; interface SetupDeps { embeddable: EmbeddableSetup; usageCollection: UsageCollectionSetup; taskManager: TaskManagerSetupContract; + contentManagement: ContentManagementServerSetup; } interface StartDeps { @@ -58,6 +60,14 @@ export class DashboardPlugin }) ); + plugins.contentManagement.register({ + id: CONTENT_ID, + storage: new DashboardStorage(), + version: { + latest: LATEST_VERSION, + }, + }); + if (plugins.taskManager) { initializeDashboardTelemetryTask(this.logger, core, plugins.taskManager, plugins.embeddable); } diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts index 25a4986208d31..2393e2853d8e1 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedDashboardPanel } from '../../common'; +import { SavedDashboardPanel } from '../../common/content_management'; import { getEmptyDashboardData, collectPanelsByType } from './dashboard_telemetry'; import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts index 1e8a0192ec38a..ea95ca6059fae 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts @@ -7,16 +7,13 @@ */ import { isEmpty } from 'lodash'; -import { SavedObjectAttributes } from '@kbn/core/server'; -import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; -import { - type ControlGroupTelemetry, - CONTROL_GROUP_TYPE, - RawControlGroupAttributes, -} from '@kbn/controls-plugin/common'; -import { initializeControlGroupTelemetry } from '@kbn/controls-plugin/server'; + import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; -import type { SavedDashboardPanel } from '../../common'; +import { initializeControlGroupTelemetry } from '@kbn/controls-plugin/server'; +import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; +import { type ControlGroupTelemetry, CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; + +import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management'; import { TASK_ID, DashboardTelemetryTaskState } from './dashboard_telemetry_collection_task'; export interface DashboardCollectorData { panels: { @@ -90,13 +87,11 @@ export const collectPanelsByType = ( export const controlsCollectorFactory = (embeddableService: EmbeddablePersistableStateService) => - (attributes: SavedObjectAttributes, collectorData: DashboardCollectorData) => { - const controlGroupAttributes: RawControlGroupAttributes | undefined = - attributes.controlGroupInput as unknown as RawControlGroupAttributes; - if (!isEmpty(controlGroupAttributes)) { + (attributes: DashboardAttributes, collectorData: DashboardCollectorData) => { + if (!isEmpty(attributes.controlGroupInput)) { collectorData.controls = embeddableService.telemetry( { - ...controlGroupAttributes, + ...attributes.controlGroupInput, type: CONTROL_GROUP_TYPE, id: `DASHBOARD_${CONTROL_GROUP_TYPE}`, }, diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts index bac38fdbb5c75..549481770f254 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts @@ -6,21 +6,25 @@ * Side Public License, v 1. */ -import { CoreSetup, Logger, SavedObjectAttributes, SavedObjectReference } from '@kbn/core/server'; import moment from 'moment'; + import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { CoreSetup, Logger, SavedObjectReference } from '@kbn/core/server'; + import { controlsCollectorFactory, collectPanelsByType, getEmptyDashboardData, DashboardCollectorData, } from './dashboard_telemetry'; -import { injectReferences, SavedDashboardPanel } from '../../common'; +import { injectReferences } from '../../common'; +import { DashboardAttributesAndReferences } from '../../common/types'; +import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management'; // This task is responsible for running daily and aggregating all the Dashboard telemerty data // into a single document. This is an effort to make sure the load of fetching/parsing all of the @@ -28,11 +32,6 @@ import { injectReferences, SavedDashboardPanel } from '../../common'; const TELEMETRY_TASK_TYPE = 'dashboard_telemetry'; export const TASK_ID = `Dashboard-${TELEMETRY_TASK_TYPE}`; -interface SavedObjectAttributesAndReferences { - attributes: SavedObjectAttributes; - references: SavedObjectReference[]; -} - export interface DashboardTelemetryTaskState { runs: number; telemetry: DashboardCollectorData; @@ -92,7 +91,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: async run() { let dashboardData = getEmptyDashboardData(); const controlsCollector = controlsCollectorFactory(embeddable); - const processDashboards = (dashboards: SavedObjectAttributesAndReferences[]) => { + const processDashboards = (dashboards: DashboardAttributesAndReferences[]) => { for (const dashboard of dashboards) { const attributes = injectReferences(dashboard, { embeddablePersistableStateService: embeddable, @@ -133,7 +132,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: const esClient = await getEsClient(); let result = await esClient.search<{ - dashboard: SavedObjectAttributes; + dashboard: DashboardAttributes; references: SavedObjectReference[]; }>(searchParams); @@ -148,8 +147,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: } return undefined; }) - .filter( - (s): s is SavedObjectAttributesAndReferences => s !== undefined + .filter( + (s): s is DashboardAttributesAndReferences => s !== undefined ) ); @@ -167,8 +166,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: } return undefined; }) - .filter( - (s): s is SavedObjectAttributesAndReferences => s !== undefined + .filter( + (s): s is DashboardAttributesAndReferences => s !== undefined ) ); } diff --git a/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts b/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts deleted file mode 100644 index c5e8da8acbd4a..0000000000000 --- a/src/plugins/dashboard/server/usage/find_by_value_embeddables.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { SavedDashboardPanel } from '../../common'; -import { findByValueEmbeddables } from './find_by_value_embeddables'; - -const visualizationByValue = { - embeddableConfig: { - value: 'visualization-by-value', - }, - type: 'visualization', -} as unknown as SavedDashboardPanel; - -const mapByValue = { - embeddableConfig: { - value: 'map-by-value', - }, - type: 'map', -} as unknown as SavedDashboardPanel; - -const embeddableByRef = { - panelRefName: 'panel_ref_1', -} as unknown as SavedDashboardPanel; - -describe('findByValueEmbeddables', () => { - it('finds the by value embeddables for the given type', async () => { - const savedObjectsResult = { - saved_objects: [ - { - attributes: { - panelsJSON: JSON.stringify([visualizationByValue, mapByValue, embeddableByRef]), - }, - }, - { - attributes: { - panelsJSON: JSON.stringify([embeddableByRef, mapByValue, visualizationByValue]), - }, - }, - ], - }; - const savedObjectClient = { find: jest.fn().mockResolvedValue(savedObjectsResult) }; - - const maps = await findByValueEmbeddables(savedObjectClient, 'map'); - - expect(maps.length).toBe(2); - expect(maps[0]).toEqual(mapByValue.embeddableConfig); - expect(maps[1]).toEqual(mapByValue.embeddableConfig); - - const visualizations = await findByValueEmbeddables(savedObjectClient, 'visualization'); - - expect(visualizations.length).toBe(2); - expect(visualizations[0]).toEqual(visualizationByValue.embeddableConfig); - expect(visualizations[1]).toEqual(visualizationByValue.embeddableConfig); - }); -}); diff --git a/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts b/src/plugins/dashboard/server/usage/find_by_value_embeddables.ts deleted file mode 100644 index 502ba828944d4..0000000000000 --- a/src/plugins/dashboard/server/usage/find_by_value_embeddables.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 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 { ISavedObjectsRepository, SavedObjectAttributes } from '@kbn/core/server'; -import type { SavedDashboardPanel } from '../../common'; - -export const findByValueEmbeddables = async ( - savedObjectClient: Pick, - embeddableType: string -) => { - const dashboards = await savedObjectClient.find({ - type: 'dashboard', - }); - - return dashboards.saved_objects - .map((dashboard) => { - try { - return JSON.parse( - dashboard.attributes.panelsJSON as string - ) as unknown as SavedDashboardPanel[]; - } catch (exception) { - return []; - } - }) - .flat() - .filter((panel) => (panel as Record).panelRefName === undefined) - .filter((panel) => panel.type === embeddableType) - .map((panel) => panel.embeddableConfig); -}; diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 4ecdedf6268da..78c7ba0dccd52 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", + "outDir": "target/types" }, "include": ["*.ts", ".storybook/**/*.ts", "common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ @@ -33,6 +33,8 @@ "@kbn/unified-search-plugin", "@kbn/shared-ux-page-analytics-no-data", "@kbn/content-management-table-list", + "@kbn/content-management-plugin", + "@kbn/content-management-utils", "@kbn/i18n-react", "@kbn/expressions-plugin", "@kbn/field-formats-plugin", @@ -48,7 +50,6 @@ "@kbn/core-overlays-browser-mocks", "@kbn/core-theme-browser-mocks", "@kbn/core-ui-settings-browser-mocks", - "@kbn/core-saved-objects-common", "@kbn/task-manager-plugin", "@kbn/core-execution-context-common", "@kbn/core-custom-branding-browser", @@ -58,8 +59,8 @@ "@kbn/shared-ux-button-toolbar", "@kbn/core-saved-objects-server", "@kbn/core-saved-objects-utils-server", + "@kbn/object-versioning", + "@kbn/core-saved-objects-api-server" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index f0b55de8ffeff..7b5b2700f72c7 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -34,6 +34,7 @@ interface Arguments { timeFields?: string[]; probability?: number; samplerSeed?: number; + ignoreGlobalFilters?: boolean; } export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition< @@ -111,6 +112,12 @@ export const getEsaggsMeta: () => Omit 'The seed to generate the random sampling of documents. Uses random sampler.', }), }, + ignoreGlobalFilters: { + types: ['boolean'], + help: i18n.translate('data.search.functions.esaggs.ignoreGlobalFilters.help', { + defaultMessage: 'Whether to ignore or use global query and filters', + }), + }, }, }); diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index 1a04e4ffeb839..de35e0b238da9 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -153,4 +153,28 @@ describe('esaggs expression function - public', () => { }) ); }); + + test('does not forward filters and query if ignoreGlobalFilters is enabled', async () => { + const input = { + type: 'kibana_context' as 'kibana_context', + filters: [{ $state: {}, meta: {}, query: {} }], + query: { + query: 'hiya', + language: 'painless', + }, + timeRange: { from: 'a', to: 'b' }, + } as KibanaContext; + + await definition() + .fn(input, { ...args, ignoreGlobalFilters: true }, mockHandlers) + .toPromise(); + + expect(handleEsaggsRequest).toHaveBeenCalledWith( + expect.objectContaining({ + filters: undefined, + query: undefined, + timeRange: input.timeRange, + }) + ); + }); }); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index b82401d4d8caf..eecaa5890ba35 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -66,10 +66,10 @@ export function getFunctionDefinition({ return handleEsaggsRequest({ abortSignal, aggs: aggConfigs, - filters: get(input, 'filters', undefined), + filters: args.ignoreGlobalFilters ? undefined : get(input, 'filters', undefined), indexPattern, inspectorAdapters, - query: get(input, 'query', undefined) as any, + query: args.ignoreGlobalFilters ? undefined : (get(input, 'query', undefined) as any), searchSessionId: getSearchSessionId(), searchSourceService: searchSource, timeFields: args.timeFields, diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index 199756cca6ee2..bbf57603d0c9b 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -80,6 +80,9 @@ export interface PanelState & { id: string }; + + // allows individual embeddable panels to maintain versioning information separate from the main Kibana version + version?: string; } export type EmbeddableStateWithType = EmbeddableInput & { type: string }; diff --git a/src/plugins/telemetry/public/components/opt_in_message.test.tsx b/src/plugins/telemetry/public/components/opt_in_message.test.tsx index ddbf4b8473454..4f55c8433ea5f 100644 --- a/src/plugins/telemetry/public/components/opt_in_message.test.tsx +++ b/src/plugins/telemetry/public/components/opt_in_message.test.tsx @@ -37,7 +37,7 @@ describe('OptInMessage', () => { }); it('claims that telemetry is enabled', () => { - expect(dom.text()).toContain('Usage collection (also known as Telemetry) is enabled.'); + expect(dom.text()).toContain('Usage collection is enabled.'); }); it('offers the link to disable it', () => { @@ -65,7 +65,7 @@ describe('OptInMessage', () => { }); it('claims that telemetry is disabled', () => { - expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.'); + expect(dom.text()).toContain('Usage collection is disabled.'); }); it('offers the link to enable it', () => { @@ -93,7 +93,7 @@ describe('OptInMessage', () => { }); it('claims that telemetry is disabled', () => { - expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.'); + expect(dom.text()).toContain('Usage collection is disabled.'); }); it('offers the link to enable it', () => { diff --git a/src/plugins/telemetry/public/components/opt_in_message.tsx b/src/plugins/telemetry/public/components/opt_in_message.tsx index e9e167c888e4b..be9724722077e 100644 --- a/src/plugins/telemetry/public/components/opt_in_message.tsx +++ b/src/plugins/telemetry/public/components/opt_in_message.tsx @@ -30,18 +30,24 @@ export const OptInMessage: React.FC = ({ + {telemetryService.isOptedIn ? ( - + ) : ( - + )} - + ), privacyStatementLink: ( /* eslint-disable-next-line @elastic/eui/href-or-on-click */ diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index 0babaac94cb48..d713986321f2d 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -63,7 +63,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = ` "description":

    { {

    label={this.getLabel(filter)} checked={this.isFilterSelected(i)} onChange={() => this.toggleFilterSelected(i)} + css={css` + word-break: break-word; + `} /> ))} diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts index 27d1f8a674939..17a14530bf9e5 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts @@ -98,6 +98,7 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim layerId, columns: columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration: getConfiguration( diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts index 0dbb88775aae2..2e889466ca978 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts @@ -95,6 +95,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time layerId, columns: columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration: getConfiguration( diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts index 1cf122de73fe3..bd7b72a1e7e2d 100644 --- a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts @@ -89,6 +89,7 @@ export const convertToLens: ConvertHeatmapToLensVisualization = async (vis, time layerId, columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration, diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts index 98f3c865ab4a5..d38fc97697774 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts @@ -85,6 +85,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti layerId, columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration: getConfiguration( diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts index d9f80e4645243..e997696e5fb01 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -73,6 +73,7 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt layerId, columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration: getConfiguration(layerId, vis, layerConfig), diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index 66b61250648d1..1866ac5f67cf7 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -99,6 +99,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi layerId, columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], + ignoreGlobalFilters: false, }, ], configuration: getConfiguration(layerId, vis.params, layerConfig), diff --git a/src/plugins/vis_types/timeseries/public/application/components/yes_no.tsx b/src/plugins/vis_types/timeseries/public/application/components/yes_no.tsx index ad01a8577e1cf..652a82b679693 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/yes_no.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/yes_no.tsx @@ -23,7 +23,7 @@ export function YesNo({ name, value, disabled, - 'data-test-subj': dataTestSubj, + 'data-test-subj': dataTestSubj = name, onChange, }: YesNoProps) { const handleChange = useCallback( diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts index a4d611748d343..f67212b2f7d5f 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts @@ -204,4 +204,60 @@ describe('convertToLens', () => { expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); }); + + test('should carry the ignoreGlobalFilters flag if set on the panel', async () => { + mockGetMetricsColumns.mockReturnValue([metricColumn]); + mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); + mockGetConfigurationForGauge.mockReturnValue({}); + + const result = await convertToLens( + { + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + ignore_global_filter: 1, + }), + } as Vis, + undefined, + true + ); + expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true); + }); + + test('should carry the ignoreGlobalFilters flag if set on the series', async () => { + mockGetMetricsColumns.mockReturnValue([metricColumn]); + mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); + mockGetConfigurationForGauge.mockReturnValue({}); + + const result = await convertToLens( + { + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + ignore_global_filter: 0, + }), + } as Vis, + undefined, + true + ); + + expect(result?.layers[0].ignoreGlobalFilters).toBe(true); + }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts index 5f33a1a4f0a59..7cd09a8954701 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts @@ -101,6 +101,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( const extendedLayer: ExtendedLayer = { indexPatternId, + ignoreGlobalFilters: Boolean(model.ignore_global_filter || series.ignore_global_filter), layerId: uuidv4(), columns: [...metricsColumns, ...(bucket ? [bucket] : [])], columnOrder: [], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/__mocks__/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/__mocks__/index.ts index d27e8bdd0a2c6..d1cf412ade45c 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/__mocks__/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/__mocks__/index.ts @@ -32,6 +32,7 @@ export const createSeries = (partialSeries?: Partial): Series => ({ series_index_pattern: { id: 'test' }, series_max_bars: 0, steps: 0, + ignore_global_filter: 0, ...partialSeries, }); @@ -57,5 +58,6 @@ export const createPanel = (parialPanel?: Partial): Panel => ({ show_grid: 0, show_legend: 0, type: PANEL_TYPES.TIMESERIES, + ignore_global_filter: 0, ...parialPanel, }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts index 9185f77cb73b2..5db3e81f17be4 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts @@ -18,6 +18,17 @@ jest.mock('../palette', () => ({ getPalette: jest.fn(() => mockGetPalette()), })); +function createEmptyLensLayer(partialLayer: Partial): Layer { + return { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + ignoreGlobalFilters: false, + layerId: '0', + ...partialLayer, + }; +} + describe('getConfigurationForMetric', () => { beforeEach(() => { jest.clearAllMocks(); @@ -26,15 +37,11 @@ describe('getConfigurationForMetric', () => { const metricId = 'some-id'; const metric = { id: metricId, type: METRIC_TYPES.COUNT }; + test('should return null if no series was provided', () => { const layerId = 'layer-id-1'; const model = createPanel({ series: [] }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForMetric(model, layer); expect(config).toBeNull(); @@ -47,12 +54,7 @@ describe('getConfigurationForMetric', () => { const model = createPanel({ series: [createSeries({ metrics: [metric1] })], }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForMetric(model, layer); expect(config).toBeNull(); @@ -69,12 +71,7 @@ describe('getConfigurationForMetric', () => { createSeries({ metrics: [metric, metric2] }), ], }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForMetric(model, layer); expect(config).toBeNull(); @@ -94,7 +91,9 @@ describe('getConfigurationForMetric', () => { const model = createPanel({ series: [createSeries({ metrics: [metric, metric1] }), createSeries({ metrics: [metric2] })], }); - const layer: Layer = { + + const layer = createEmptyLensLayer({ + layerId, columns: [ { columnId: columnId1, @@ -105,10 +104,7 @@ describe('getConfigurationForMetric', () => { meta: { metricId: metricId2 }, }, ] as Column[], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + }); const config = getConfigurationForMetric(model, layer); expect(config).toEqual({ @@ -131,7 +127,7 @@ describe('getConfigurationForMetric', () => { series: [createSeries({ metrics: [metric] })], }); const bucket = { columnId: bucketColumnId } as Column; - const layer: Layer = { + const layer = createEmptyLensLayer({ columns: [ { columnId, @@ -144,10 +140,8 @@ describe('getConfigurationForMetric', () => { meta: { metricId }, }, ], - columnOrder: [], - indexPatternId: 'some-index-pattern', layerId, - }; + }); const config = getConfigurationForMetric(model, layer, bucket); expect(config).toEqual({ @@ -169,7 +163,7 @@ describe('getConfigurationForMetric', () => { const model = createPanel({ series: [createSeries({ metrics: [metric] })], }); - const layer: Layer = { + const layer = createEmptyLensLayer({ columns: [ { columnId, @@ -182,10 +176,8 @@ describe('getConfigurationForMetric', () => { meta: { metricId }, }, ], - columnOrder: [], - indexPatternId: 'some-index-pattern', layerId, - }; + }); const config = getConfigurationForMetric(model, layer); expect(config).toBeNull(); expect(mockGetPalette).toBeCalledTimes(1); @@ -215,12 +207,7 @@ describe('getConfigurationForGauge', () => { test('should return null if no series was provided', () => { const layerId = 'layer-id-1'; const model = createPanel({ series: [] }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); expect(config).toBeNull(); @@ -233,12 +220,7 @@ describe('getConfigurationForGauge', () => { const model = createPanel({ series: [createSeries({ metrics: [metric1] })], }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); expect(config).toBeNull(); @@ -252,7 +234,7 @@ describe('getConfigurationForGauge', () => { const model = createPanel({ series: [createSeries({ metrics: [metric] })], }); - const layer: Layer = { + const layer = createEmptyLensLayer({ columns: [ { columnId, @@ -265,10 +247,8 @@ describe('getConfigurationForGauge', () => { meta: { metricId }, }, ], - columnOrder: [], - indexPatternId: 'some-index-pattern', layerId, - }; + }); const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); expect(config).toBeNull(); expect(mockGetPalette).toBeCalledTimes(1); @@ -279,12 +259,7 @@ describe('getConfigurationForGauge', () => { const metric1 = { id: 'metric-id-1', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' }; const color = '#fff'; const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] }); - const layer: Layer = { - columns: [], - columnOrder: [], - indexPatternId: 'some-index-pattern', - layerId, - }; + const layer = createEmptyLensLayer({ layerId }); const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); expect(config).toEqual({ @@ -312,7 +287,7 @@ describe('getConfigurationForGauge', () => { const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] }); const bucketColumnId = 'bucket-column-id-1'; const bucket = { columnId: bucketColumnId } as Column; - const layer: Layer = { + const layer = createEmptyLensLayer({ columns: [ { columnId: columnId1, @@ -325,10 +300,8 @@ describe('getConfigurationForGauge', () => { meta: { metricId }, }, ], - columnOrder: [], - indexPatternId: 'some-index-pattern', layerId, - }; + }); const config = getConfigurationForGauge(model, layer, bucket, gaugeMaxColumn); expect(config).toEqual({ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts index c344cac8f9b6c..d6592a04db2a8 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts @@ -68,6 +68,7 @@ describe('getLayers', () => { { indexPatternId: 'test', layerId: 'test-layer-1', + ignoreGlobalFilters: false, columns: [ { operationType: 'count', @@ -111,6 +112,7 @@ describe('getLayers', () => { { indexPatternId: 'test', layerId: 'test-layer-1', + ignoreGlobalFilters: false, columns: [ { operationType: 'static_value', @@ -131,6 +133,7 @@ describe('getLayers', () => { { indexPatternId: 'test', layerId: 'test-layer-1', + ignoreGlobalFilters: false, columns: [ { operationType: 'percentile', @@ -164,6 +167,7 @@ describe('getLayers', () => { { indexPatternId: 'test', layerId: 'test-layer-1', + ignoreGlobalFilters: false, columns: [ { operationType: 'percentile_rank', diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts index fd0c07dc4ff94..790ab6dc6946c 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts @@ -283,4 +283,73 @@ describe('convertToLens', () => { expect(result?.type).toBe('lnsMetric'); expect(mockGetConfigurationForMetric).toBeCalledTimes(1); }); + + test('should set the ignoreGlobalFilters if set on the panel', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + ignore_global_filter: 1, + }), + } as Vis); + expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true); + }); + + test('should set the ignoreGlobalFilters if set on the series', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(true); + }); + + test('should ignore the ignoreGlobalFilters if set on hidden series', async () => { + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetBucketsColumns.mockReturnValueOnce([bucket]); + mockGetMetricsColumns.mockReturnValueOnce([metric]); + + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(false); + }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts index 95c7d0d6db0e3..d992baea9c0fb 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts @@ -117,6 +117,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( const [bucket] = uniqueBuckets; const extendedLayer: ExtendedLayer = { + ignoreGlobalFilters: Boolean( + model.ignore_global_filter || + // eslint-disable-next-line @typescript-eslint/naming-convention + visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter) + ), indexPatternId: indexPatternId as string, layerId: uuidv4(), columns: [...metrics, ...(bucket ? [bucket] : [])], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts index c5989a288cd89..a6dee15854b04 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts @@ -232,4 +232,65 @@ describe('convertToLens', () => { expect(result?.type).toBe('lnsDatatable'); expect(mockIsValidMetrics).toBeCalledTimes(1); }); + + test('should set the ignoreGlobalFilters if set on the panel', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + ignore_global_filter: 1, + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true); + }); + + test('should set the ignoreGlobalFilters if set on the series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(true); + }); + + test('should ignore the ignoreGlobalFilters if set on hidden series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(false); + }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts index 42955cccfab2e..c518d8cfcce8e 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts @@ -38,7 +38,8 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( const dataViews = getDataViewsStart(); try { - const seriesNum = model.series.filter((series) => !series.hidden).length; + const visibleSeries = model.series.filter((series) => !series.hidden); + const seriesNum = visibleSeries.length; const sortConfig = uiState.get('table')?.sort ?? {}; const datasourceInfo = await extractOrGenerateDatasourceInfo( @@ -165,6 +166,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( } const extendedLayer: ExtendedLayer = { + ignoreGlobalFilters: Boolean( + model.ignore_global_filter || + // eslint-disable-next-line @typescript-eslint/naming-convention + visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter) + ), indexPatternId: indexPatternId as string, layerId: uuidv4(), columns: [...metrics, ...commonBucketsColumns, ...bucketsColumns], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts index dc1c51474fd07..3170beaf92ed4 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts @@ -162,4 +162,51 @@ describe('convertToLens', () => { expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); }); + + test('should set the ignoreGlobalFilters if set on the panel', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + ignore_global_filter: 1, + }), + } as Vis); + expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true); + }); + + test('should set the ignoreGlobalFilters if set on the series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + ignore_global_filter: 1, + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(true); + }); + + test('should ignore the ignoreGlobalFilters if set on hidden series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + ignore_global_filter: 1, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(false); + }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index 09c2e07ebd0b9..a35f0fdb9a124 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -111,6 +111,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ({ params: mo const layerId = uuidv4(); extendedLayers[layerIdx] = { + ignoreGlobalFilters: Boolean(model.ignore_global_filter || series.ignore_global_filter), indexPatternId, layerId, columns: isReferenceLine diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts index 404f3b42fb7ba..d57f71f3bc1a9 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts @@ -133,4 +133,51 @@ describe('convertToLens', () => { expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); }); + + test('should set the ignoreGlobalFilters if set on the panel', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + ignore_global_filter: 1, + }), + } as Vis); + expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true); + }); + + test('should set the ignoreGlobalFilters if set on the series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + ignore_global_filter: 1, + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(true); + }); + + test('should ignore the ignoreGlobalFilters if set on hidden series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + ignore_global_filter: 1, + }), + + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + }), + } as Vis); + expect(result?.layers[0].ignoreGlobalFilters).toBe(false); + }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 8f93ced2b8a83..a7f1dd9077e49 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -37,7 +37,8 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( const dataViews = getDataViewsStart(); try { const extendedLayers: Record = {}; - const seriesNum = model.series.filter((series) => !series.hidden).length; + const visibleSeries = model.series.filter((series) => !series.hidden); + const seriesNum = visibleSeries.length; // handle multiple layers/series for (const [layerIdx, series] of model.series.entries()) { @@ -85,6 +86,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ( const layerId = uuidv4(); extendedLayers[layerIdx] = { + ignoreGlobalFilters: Boolean( + model.ignore_global_filter || + // eslint-disable-next-line @typescript-eslint/naming-convention + visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter) + ), indexPatternId, layerId, columns: [...metricsColumns, ...bucketsColumns], diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts index 5aebd59c941d3..a4f404d182c40 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts @@ -28,6 +28,7 @@ describe('getConfiguration', () => { seriesIdsMap: { 1: '1' }, collapseFn: 'max', isReferenceLineLayer: false, + ignoreGlobalFilters: false, }, { indexPatternId: '', @@ -41,6 +42,7 @@ describe('getConfiguration', () => { seriesIdsMap: { 4: '2' }, collapseFn: undefined, isReferenceLineLayer: false, + ignoreGlobalFilters: false, }, { indexPatternId: '', @@ -51,6 +53,7 @@ describe('getConfiguration', () => { seriesIdsMap: {}, collapseFn: undefined, isReferenceLineLayer: true, + ignoreGlobalFilters: false, }, ]; const series = [ diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index da1c50d94672e..88aee4e1eb7b6 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -26,6 +26,7 @@ export interface Layer { seriesIdsMap: Record; isReferenceLineLayer: boolean; collapseFn?: CollapseFunction; + ignoreGlobalFilters: boolean; } const SIBBLING_PIPELINE_AGGS: string[] = [ @@ -190,6 +191,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte seriesIdsMap, collapseFn, isReferenceLineLayer: false, + ignoreGlobalFilters: false, }; }); @@ -202,6 +204,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte columnOrder: [], metrics: [staticValueColumn.columnId], isReferenceLineLayer: true, + ignoreGlobalFilters: false, collapseFn: undefined, seriesIdsMap: {}, }); diff --git a/src/plugins/visualizations/common/convert_to_lens/types/context.ts b/src/plugins/visualizations/common/convert_to_lens/types/context.ts index c894f95596983..29c7730924cad 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/context.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/context.ts @@ -14,6 +14,7 @@ export interface Layer { layerId: string; columns: Column[]; columnOrder: string[]; + ignoreGlobalFilters: boolean; } export interface NavigateToLensContext { diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index 0b22ec93c2b2c..159585d7e4a75 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -12,6 +12,7 @@ import type { FtrProviderContext } from '../../ftr_provider_context'; function ndjsonToObject(input: string) { return input.split('\n').map((str) => JSON.parse(str)); } + export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); @@ -53,6 +54,32 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('should support exporting all types', async () => { + await supertest + .post(`/s/${SPACE_ID}/api/saved_objects/_export`) + .send({ + type: ['*'], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = ndjsonToObject(resp.text); + expect(objects).to.have.length(4); + expect(objects.map((obj) => ({ id: obj.id, type: obj.type }))).to.eql([ + { id: '7.0.0-alpha1', type: 'config' }, + { + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + type: 'index-pattern', + }, + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + type: 'visualization', + }, + { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', type: 'dashboard' }, + ]); + }); + }); + it('should exclude the export details if asked', async () => { await supertest .post(`/s/${SPACE_ID}/api/saved_objects/_export`) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index ae7dcf2ae32ab..d91a21045f421 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -66,6 +66,12 @@ export class VisualBuilderPageObject extends FtrService { } } + private async toggleYesNoSwitch(testSubj: string, value: boolean) { + const option = await this.testSubjects.find(`${testSubj}-${value ? 'yes' : 'no'}`); + (await option.findByCssSelector('label')).click(); + await this.header.waitUntilLoadingHasFinished(); + } + public async checkTabIsSelected(chartType: string) { const chartTypeBtn = await this.testSubjects.find(`${chartType}TsvbTypeBtn`); const isSelected = await chartTypeBtn.getAttribute('aria-selected'); @@ -588,17 +594,11 @@ export class VisualBuilderPageObject extends FtrService { } public async setDropLastBucket(value: boolean) { - const option = await this.testSubjects.find(`metricsDropLastBucket-${value ? 'yes' : 'no'}`); - (await option.findByCssSelector('label')).click(); - await this.header.waitUntilLoadingHasFinished(); + await this.toggleYesNoSwitch('metricsDropLastBucket', value); } public async setOverrideIndexPattern(value: boolean) { - const option = await this.testSubjects.find( - `seriesOverrideIndexPattern-${value ? 'yes' : 'no'}` - ); - (await option.findByCssSelector('label')).click(); - await this.header.waitUntilLoadingHasFinished(); + await this.toggleYesNoSwitch('seriesOverrideIndexPattern', value); } public async waitForIndexPatternTimeFieldOptionsLoaded() { @@ -912,6 +912,10 @@ export class VisualBuilderPageObject extends FtrService { await this.header.waitUntilLoadingHasFinished(); } + public async setIgnoreFilters(value: boolean) { + await this.toggleYesNoSwitch('ignore_global_filter', value); + } + public async setMetricsDataTimerangeMode(value: string) { const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); return await this.comboBox.setElement(dataTimeRangeMode, value); diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts index 0b405d20c4db3..682c79047a725 100644 --- a/test/functional/services/common/find.ts +++ b/test/functional/services/common/find.ts @@ -307,6 +307,16 @@ export class FindService extends FtrService { }, timeout); } + public async existsByXpath( + selector: string, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + this.log.debug(`Find.existsByXpath('${selector}') with timeout=${timeout}`); + return await this.exists(async (drive) => { + return this.wrapAll(await drive.findElements(By.xpath(selector))); + }, timeout); + } + public async clickByCssSelectorWhenNotDisabled(selector: string, opts?: TimeoutOpt) { const timeout = opts?.timeout ?? this.defaultFindTimeout; diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index fc5be23eba61d..935495c362ab5 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -259,6 +259,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.observability.unsafe.alertDetails.metrics.enabled (boolean)', 'xpack.observability.unsafe.alertDetails.logs.enabled (boolean)', 'xpack.observability.unsafe.alertDetails.uptime.enabled (boolean)', + 'xpack.observability.unsafe.thresholdRule.enabled (boolean)', 'xpack.observability_onboarding.ui.enabled (boolean)', ]; // We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large diff --git a/x-pack/plugins/alerting/docs/openapi/bundled.json b/x-pack/plugins/alerting/docs/openapi/bundled.json index b17dbef7d29c3..8be61e53d2d69 100644 --- a/x-pack/plugins/alerting/docs/openapi/bundled.json +++ b/x-pack/plugins/alerting/docs/openapi/bundled.json @@ -564,6 +564,9 @@ "examples": { "findRulesResponse": { "$ref": "#/components/examples/find_rules_response" + }, + "findConditionalActionRulesResponse": { + "$ref": "#/components/examples/find_rules_response_conditional_action" } } } @@ -2476,6 +2479,120 @@ "items": { "type": "object", "properties": { + "alerts_filter": { + "type": "object", + "description": "Conditions that affect whether the action runs. If you specify multiple conditions, all conditions must be met for the action to run. For example, if an alert occurs within the specified time frame and matches the query, the action runs.\n", + "properties": { + "query": { + "type": "object", + "description": "Defines a query filter that determines whether the action runs.", + "properties": { + "kql": { + "type": "string", + "description": "A filter written in Kibana Query Language (KQL)." + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "description": "A filter written in Elasticsearch Query Domain Specific Language (DSL) as defined in the `kbn-es-query` package.", + "properties": { + "meta": { + "type": "object", + "properties": { + "alias": { + "type": "string", + "nullable": true + }, + "controlledBy": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "string" + }, + "group": { + "type": "string" + }, + "index": { + "type": "string" + }, + "isMultiIndex": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "object" + }, + "type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "query": { + "type": "object" + }, + "$state": { + "type": "object" + } + } + } + } + } + }, + "timeframe": { + "type": "object", + "description": "Defines a period that limits whether the action runs.", + "properties": { + "days": { + "type": "array", + "description": "Defines the days of the week that the action can run, represented as an array of numbers. For example, `1` represents Monday. An empty array is equivalent to specifying all the days of the week.", + "items": { + "type": "integer" + }, + "example": [ + 1, + 2, + 3, + 4, + 5 + ] + }, + "hours": { + "type": "object", + "description": "Defines the range of time in a day that the action can run. If the `start` value is `00:00` and the `end` value is `24:00`, actions be generated all day.\n", + "properties": { + "end": { + "type": "string", + "description": "The end of the time frame in 24-hour notation (`hh:mm`).", + "example": "17:00" + }, + "start": { + "type": "string", + "description": "The start of the time frame in 24-hour notation (`hh:mm`).", + "example": "08:00" + } + } + }, + "timezone": { + "type": "string", + "description": "The ISO time zone for the `hours` values. Values such as `UTC` and `UTC+1` also work but lack built-in daylight savings time support and are not recommended.\n", + "example": "Europe/Madrid" + } + } + } + } + }, "connector_type_id": { "type": "string", "description": "The type of connector. This property appears in responses but cannot be set in requests.", @@ -2651,9 +2768,14 @@ "example": "succeeded" }, "outcome_msg": { - "type": "string", - "nullable": true, - "example": null + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "outcome_order": { + "type": "integer" }, "warning": { "type": "string", @@ -3364,6 +3486,168 @@ ] } }, + "find_rules_response_conditional_action": { + "summary": "Retrieve information about a rule that has conditional actions.", + "value": { + "page": 1, + "total": 1, + "per_page": 10, + "data": [ + { + "id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb", + "name": "security_rule", + "consumer": "siem", + "enabled": true, + "tags": [], + "throttle": null, + "revision": 1, + "running": false, + "schedule": { + "interval": "1m" + }, + "params": { + "author": [], + "description": "A security threshold rule.", + "ruleId": "an_internal_rule_id", + "falsePositives": [], + "from": "now-3660s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1h", + "kibana_siem_app_url": "https://localhost:5601/app/security" + }, + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "threshold", + "language": "kuery", + "index": [ + "kibana_sample_data_logs" + ], + "query": "*", + "filters": [], + "threshold": { + "field": [ + "bytes" + ], + "value": 1, + "cardinality": [] + } + }, + "rule_type_id": "siem.thresholdRule", + "created_by": "elastic", + "updated_by": "elastic", + "created_at": "2023-05-16T15:50:28.358Z", + "updated_at": "2023-05-16T20:25:42.559Z", + "api_key_owner": "elastic", + "notify_when": null, + "mute_all": false, + "muted_alert_ids": [], + "scheduled_task_id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb", + "execution_status": { + "status": "ok", + "last_execution_date": "2023-05-16T20:26:49.590Z", + "last_duration": 166 + }, + "actions": [ + { + "group": "default", + "id": "49eae970-f401-11ed-9f8e-399c75a2deeb", + "params": { + "documents": [ + { + "rule_id": { + "[object Object]": null + }, + "rule_name": { + "[object Object]": null + }, + "alert_id": { + "[object Object]": null + }, + "context_message": { + "[object Object]": null + } + } + ] + }, + "connector_type_id": ".index", + "frequency": { + "summary": true, + "notify_when": "onActiveAlert", + "throttle": null + }, + "uuid": "1c7a1280-f28c-4e06-96b2-e4e5f05d1d61", + "alerts_filter": { + "timeframe": { + "days": [ + 7 + ], + "timezone": "UTC", + "hours": { + "start": "08:00", + "end": "17:00" + } + }, + "query": { + "kql": "", + "filters": [ + { + "meta": { + "disabled": false, + "negate": false, + "alias": null, + "index": "c4bdca79-e69e-4d80-82a1-e5192c621bea", + "key": "client.geo.region_iso_code", + "field": "client.geo.region_iso_code", + "params": { + "query": "CA-QC", + "type": "phrase" + } + }, + "$state": { + "store": "appState" + }, + "query": { + "match_phrase": { + "client.geo.region_iso_code": "CA-QC" + } + } + } + ] + } + } + } + ], + "last_run": { + "alerts_count": { + "new": 0, + "ignored": 0, + "recovered": 0, + "active": 0 + }, + "outcome_msg": [ + "Rule execution completed successfully" + ], + "outcome_order": 0, + "warning": null, + "outcome": "succeeded" + }, + "next_run": "2023-05-16T20:27:49.507Z", + "api_key_created_by_user": false + } + ] + } + }, "get_health_response": { "summary": "Retrieve information about the health of the alerting framework.", "value": { diff --git a/x-pack/plugins/alerting/docs/openapi/bundled.yaml b/x-pack/plugins/alerting/docs/openapi/bundled.yaml index a734948511606..1d5881c0c2304 100644 --- a/x-pack/plugins/alerting/docs/openapi/bundled.yaml +++ b/x-pack/plugins/alerting/docs/openapi/bundled.yaml @@ -342,6 +342,8 @@ paths: examples: findRulesResponse: $ref: '#/components/examples/find_rules_response' + findConditionalActionRulesResponse: + $ref: '#/components/examples/find_rules_response_conditional_action' '401': description: Authorization information is missing or invalid. content: @@ -681,7 +683,7 @@ paths: summary: Mutes an alert. operationId: muteAlert description: | - You must have `all` privileges for the appropriate Kibana features, depending on the `consumer` and `rule_type_id` of the rule. For example, the **Management > Stack Rules** feature, **Analytics > Discover** and **Machine Learning** features, **Observability**, and **Security** features. If the rule has actions, you must also have `read` privileges for the **Management > Actions and Connectors** feature. + You must have `all` privileges for the appropriate Kibana features, depending on the `consumer` and `rule_type_id` of the rule. For example, the **Management > Stack Rules** feature, **Analytics > Discover** and **Machine Learning** features, **Observability**, and **Security** features. If the rule has actions, you must also have `read` privileges for the **Management > Actions and Connectors** feature. tags: - alerting parameters: @@ -707,7 +709,7 @@ paths: summary: Unmutes an alert. operationId: unmuteAlert description: | - You must have `all` privileges for the appropriate Kibana features, depending on the `consumer` and `rule_type_id` of the rule. For example, the **Management > Stack Rules** feature, **Analytics > Discover** and **Machine Learning** features, **Observability**, and **Security** features. If the rule has actions, you must also have `read` privileges for the **Management > Actions and Connectors** feature. + You must have `all` privileges for the appropriate Kibana features, depending on the `consumer` and `rule_type_id` of the rule. For example, the **Management > Stack Rules** feature, **Analytics > Discover** and **Machine Learning** features, **Observability**, and **Security** features. If the rule has actions, you must also have `read` privileges for the **Management > Actions and Connectors** feature. tags: - alerting parameters: @@ -1571,6 +1573,89 @@ components: items: type: object properties: + alerts_filter: + type: object + description: | + Conditions that affect whether the action runs. If you specify multiple conditions, all conditions must be met for the action to run. For example, if an alert occurs within the specified time frame and matches the query, the action runs. + properties: + query: + type: object + description: Defines a query filter that determines whether the action runs. + properties: + kql: + type: string + description: A filter written in Kibana Query Language (KQL). + filters: + type: array + items: + type: object + description: A filter written in Elasticsearch Query Domain Specific Language (DSL) as defined in the `kbn-es-query` package. + properties: + meta: + type: object + properties: + alias: + type: string + nullable: true + controlledBy: + type: string + disabled: + type: boolean + field: + type: string + group: + type: string + index: + type: string + isMultiIndex: + type: boolean + key: + type: string + negate: + type: boolean + params: + type: object + type: + type: string + value: + type: string + query: + type: object + $state: + type: object + timeframe: + type: object + description: Defines a period that limits whether the action runs. + properties: + days: + type: array + description: Defines the days of the week that the action can run, represented as an array of numbers. For example, `1` represents Monday. An empty array is equivalent to specifying all the days of the week. + items: + type: integer + example: + - 1 + - 2 + - 3 + - 4 + - 5 + hours: + type: object + description: | + Defines the range of time in a day that the action can run. If the `start` value is `00:00` and the `end` value is `24:00`, actions be generated all day. + properties: + end: + type: string + description: The end of the time frame in 24-hour notation (`hh:mm`). + example: '17:00' + start: + type: string + description: The start of the time frame in 24-hour notation (`hh:mm`). + example: '08:00' + timezone: + type: string + description: | + The ISO time zone for the `hours` values. Values such as `UTC` and `UTC+1` also work but lack built-in daylight savings time support and are not recommended. + example: Europe/Madrid connector_type_id: type: string description: The type of connector. This property appears in responses but cannot be set in requests. @@ -1708,9 +1793,12 @@ components: type: string example: succeeded outcome_msg: - type: string + type: array + items: + type: string nullable: true - example: null + outcome_order: + type: integer warning: type: string nullable: true @@ -2284,6 +2372,128 @@ components: outcome: succeeded next_run: '2022-12-06T01:45:23.912Z' api_key_created_by_user: false + find_rules_response_conditional_action: + summary: Retrieve information about a rule that has conditional actions. + value: + page: 1 + total: 1 + per_page: 10 + data: + - id: 6107a8f0-f401-11ed-9f8e-399c75a2deeb + name: security_rule + consumer: siem + enabled: true + tags: [] + throttle: null + revision: 1 + running: false + schedule: + interval: 1m + params: + author: [] + description: A security threshold rule. + ruleId: an_internal_rule_id + falsePositives: [] + from: now-3660s + immutable: false + license: '' + outputIndex: '' + meta: + from: 1h + kibana_siem_app_url: https://localhost:5601/app/security + maxSignals: 100 + riskScore: 21 + riskScoreMapping: [] + severity: low + severityMapping: [] + threat: [] + to: now + references: [] + version: 1 + exceptionsList: [] + type: threshold + language: kuery + index: + - kibana_sample_data_logs + query: '*' + filters: [] + threshold: + field: + - bytes + value: 1 + cardinality: [] + rule_type_id: siem.thresholdRule + created_by: elastic + updated_by: elastic + created_at: '2023-05-16T15:50:28.358Z' + updated_at: '2023-05-16T20:25:42.559Z' + api_key_owner: elastic + notify_when: null + mute_all: false + muted_alert_ids: [] + scheduled_task_id: 6107a8f0-f401-11ed-9f8e-399c75a2deeb + execution_status: + status: ok + last_execution_date: '2023-05-16T20:26:49.590Z' + last_duration: 166 + actions: + - group: default + id: 49eae970-f401-11ed-9f8e-399c75a2deeb + params: + documents: + - rule_id: + '[object Object]': null + rule_name: + '[object Object]': null + alert_id: + '[object Object]': null + context_message: + '[object Object]': null + connector_type_id: .index + frequency: + summary: true + notify_when: onActiveAlert + throttle: null + uuid: 1c7a1280-f28c-4e06-96b2-e4e5f05d1d61 + alerts_filter: + timeframe: + days: + - 7 + timezone: UTC + hours: + start: '08:00' + end: '17:00' + query: + kql: '' + filters: + - meta: + disabled: false + negate: false + alias: null + index: c4bdca79-e69e-4d80-82a1-e5192c621bea + key: client.geo.region_iso_code + field: client.geo.region_iso_code + params: + query: CA-QC + type: phrase + $state: + store: appState + query: + match_phrase: + client.geo.region_iso_code: CA-QC + last_run: + alerts_count: + new: 0 + ignored: 0 + recovered: 0 + active: 0 + outcome_msg: + - Rule execution completed successfully + outcome_order: 0 + warning: null + outcome: succeeded + next_run: '2023-05-16T20:27:49.507Z' + api_key_created_by_user: false get_health_response: summary: Retrieve information about the health of the alerting framework. value: diff --git a/x-pack/plugins/alerting/docs/openapi/components/examples/find_rules_response_conditional_action.yaml b/x-pack/plugins/alerting/docs/openapi/components/examples/find_rules_response_conditional_action.yaml new file mode 100644 index 0000000000000..857153be8fa38 --- /dev/null +++ b/x-pack/plugins/alerting/docs/openapi/components/examples/find_rules_response_conditional_action.yaml @@ -0,0 +1,114 @@ +summary: Retrieve information about a rule that has conditional actions. +value: + page: 1 + total: 1 + per_page: 10 + data: + - id: 6107a8f0-f401-11ed-9f8e-399c75a2deeb + name: security_rule + consumer: siem + enabled: true + tags: [] + throttle: null + revision: 1 + running: false + schedule: + interval: 1m + params: + author: [] + description: A security threshold rule. + ruleId: an_internal_rule_id + falsePositives: [] + from: now-3660s + immutable: false + license: "" + outputIndex: "" + meta: + from: 1h + kibana_siem_app_url: https://localhost:5601/app/security + maxSignals: 100 + riskScore: 21 + riskScoreMapping: [] + severity: low + severityMapping: [] + threat: [] + to: now + references: [] + version: 1 + exceptionsList: [] + type: threshold + language: kuery + index: ["kibana_sample_data_logs"] + query: "*" + filters: [] + threshold: + field: ["bytes"] + value: 1 + cardinality: [] + rule_type_id: siem.thresholdRule + created_by: elastic + updated_by: elastic + created_at: '2023-05-16T15:50:28.358Z' + updated_at: '2023-05-16T20:25:42.559Z' + api_key_owner: elastic + notify_when: null + mute_all: false + muted_alert_ids: [] + scheduled_task_id: 6107a8f0-f401-11ed-9f8e-399c75a2deeb + execution_status: + status: ok + last_execution_date: '2023-05-16T20:26:49.590Z' + last_duration: 166 + actions: + - group: default + id: 49eae970-f401-11ed-9f8e-399c75a2deeb + params: + documents: + - rule_id: {{rule.id}} + rule_name: {{rule.name}} + alert_id: {{alert.id}} + context_message: {{context.message}} + connector_type_id: .index + frequency: + summary: true + notify_when: onActiveAlert + throttle: null + uuid: 1c7a1280-f28c-4e06-96b2-e4e5f05d1d61 + alerts_filter: + timeframe: + days: [7] + timezone: UTC + hours: + start: 08:00 + end: 17:00 + query: + kql: "" + filters: + - meta: + disabled: false + negate: false + alias: null + index: c4bdca79-e69e-4d80-82a1-e5192c621bea + key: client.geo.region_iso_code + field: client.geo.region_iso_code + params: + query: CA-QC + type: phrase + $state: + store: appState + query: + match_phrase: + client.geo.region_iso_code: CA-QC + last_run: + alerts_count: + new: 0 + ignored: 0 + recovered: 0 + active: 0 + outcome_msg: + - Rule execution completed successfully + outcome_order: 0 + warning: null + outcome: succeeded + next_run: '2023-05-16T20:27:49.507Z' + api_key_created_by_user: false diff --git a/x-pack/plugins/alerting/docs/openapi/components/schemas/actions.yaml b/x-pack/plugins/alerting/docs/openapi/components/schemas/actions.yaml index 5b67d45ced76f..2410363de6d01 100644 --- a/x-pack/plugins/alerting/docs/openapi/components/schemas/actions.yaml +++ b/x-pack/plugins/alerting/docs/openapi/components/schemas/actions.yaml @@ -8,6 +8,88 @@ nullable: true items: type: object properties: + alerts_filter: + type: object + description: > + Conditions that affect whether the action runs. + If you specify multiple conditions, all conditions must be met for the action to run. + For example, if an alert occurs within the specified time frame and matches the query, the action runs. + properties: + query: + type: object + description: Defines a query filter that determines whether the action runs. + properties: + kql: + type: string + description: A filter written in Kibana Query Language (KQL). + filters: + type: array + items: + type: object + description: A filter written in Elasticsearch Query Domain Specific Language (DSL) as defined in the `kbn-es-query` package. + properties: + meta: + type: object + properties: + alias: + type: string + nullable: true + controlledBy: + type: string + disabled: + type: boolean + field: + type: string + group: + type: string + index: + type: string + isMultiIndex: + type: boolean + key: + type: string + negate: + type: boolean + params: + type: object + type: + type: string + value: + type: string + query: + type: object + $state: + type: object + timeframe: + type: object + description: Defines a period that limits whether the action runs. + properties: + days: + type: array + description: Defines the days of the week that the action can run, represented as an array of numbers. For example, `1` represents Monday. An empty array is equivalent to specifying all the days of the week. + items: + type: integer + example: [1,2,3,4,5] + hours: + type: object + description: > + Defines the range of time in a day that the action can run. + If the `start` value is `00:00` and the `end` value is `24:00`, actions be generated all day. + properties: + end: + type: string + description: The end of the time frame in 24-hour notation (`hh:mm`). + example: 17:00 + start: + type: string + description: The start of the time frame in 24-hour notation (`hh:mm`). + example: 08:00 + timezone: + type: string + description: > + The ISO time zone for the `hours` values. + Values such as `UTC` and `UTC+1` also work but lack built-in daylight savings time support and are not recommended. + example: Europe/Madrid connector_type_id: type: string description: The type of connector. This property appears in responses but cannot be set in requests. diff --git a/x-pack/plugins/alerting/docs/openapi/components/schemas/rule_response_properties.yaml b/x-pack/plugins/alerting/docs/openapi/components/schemas/rule_response_properties.yaml index d2d37b8f3736a..d49d111582ebb 100644 --- a/x-pack/plugins/alerting/docs/openapi/components/schemas/rule_response_properties.yaml +++ b/x-pack/plugins/alerting/docs/openapi/components/schemas/rule_response_properties.yaml @@ -85,9 +85,12 @@ properties: type: string example: succeeded outcome_msg: - type: string + type: array + items: + type: string nullable: true - example: null + outcome_order: + type: integer warning: type: string nullable: true diff --git a/x-pack/plugins/alerting/docs/openapi/paths/s@{spaceid}@api@alerting@rules@_find.yaml b/x-pack/plugins/alerting/docs/openapi/paths/s@{spaceid}@api@alerting@rules@_find.yaml index 2f84059aa392d..3b58806865f43 100644 --- a/x-pack/plugins/alerting/docs/openapi/paths/s@{spaceid}@api@alerting@rules@_find.yaml +++ b/x-pack/plugins/alerting/docs/openapi/paths/s@{spaceid}@api@alerting@rules@_find.yaml @@ -113,6 +113,8 @@ get: examples: findRulesResponse: $ref: '../components/examples/find_rules_response.yaml' + findConditionalActionRulesResponse: + $ref: '../components/examples/find_rules_response_conditional_action.yaml' '401': description: Authorization information is missing or invalid. content: diff --git a/x-pack/plugins/cases/common/api/cases/status.ts b/x-pack/plugins/cases/common/api/cases/status.ts index e1d342e0d6dad..76aefb7aba765 100644 --- a/x-pack/plugins/cases/common/api/cases/status.ts +++ b/x-pack/plugins/cases/common/api/cases/status.ts @@ -6,7 +6,7 @@ */ import * as rt from 'io-ts'; -import { CaseStatuses } from '@kbn/cases-components'; +import { CaseStatuses } from '@kbn/cases-components/src/status/types'; export { CaseStatuses }; diff --git a/x-pack/plugins/cases/common/api/helpers.ts b/x-pack/plugins/cases/common/api/helpers.ts index b02d46e85fb18..9e33eb3173d0e 100644 --- a/x-pack/plugins/cases/common/api/helpers.ts +++ b/x-pack/plugins/cases/common/api/helpers.ts @@ -10,7 +10,6 @@ import { CASE_METRICS_DETAILS_URL, CASE_COMMENTS_URL, CASE_USER_ACTIONS_URL, - CASE_COMMENT_DETAILS_URL, CASE_PUSH_URL, CASE_CONFIGURE_DETAILS_URL, CASE_ALERTS_URL, @@ -36,10 +35,6 @@ export const getCaseCommentsUrl = (id: string): string => { return CASE_COMMENTS_URL.replace('{case_id}', id); }; -export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): string => { - return CASE_COMMENT_DETAILS_URL.replace('{case_id}', caseId).replace('{comment_id}', commentId); -}; - export const getCaseFindAttachmentsUrl = (caseId: string): string => { return CASE_FIND_ATTACHMENTS_URL.replace('{case_id}', caseId); }; diff --git a/x-pack/plugins/cases/common/constants/mime_types.ts b/x-pack/plugins/cases/common/constants/mime_types.ts index c35e5ef674c81..d7f26a7d1c792 100644 --- a/x-pack/plugins/cases/common/constants/mime_types.ts +++ b/x-pack/plugins/cases/common/constants/mime_types.ts @@ -8,7 +8,7 @@ /** * These were retrieved from https://www.iana.org/assignments/media-types/media-types.xhtml#image */ -export const imageMimeTypes = [ +const imageMimeTypes = [ 'image/aces', 'image/apng', 'image/avci', diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index 2c06bc53a1b1b..4733eab1b72b9 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -15,6 +15,16 @@ // For example, constants below could eventually be in a "kbn-cases-constants" instead. // See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api +export type { Case, Cases, CasesBulkGetResponse } from './api'; +export type { + CaseUI, + CasesUI, + CasesFindResponseUI, + Ecs, + CaseViewRefreshPropInterface, + CasesPermissions, +} from './ui/types'; + export { APP_ID, CASES_URL, @@ -38,21 +48,6 @@ export { throwErrors, ExternalReferenceStorageType, } from './api'; - -export type { Case, Cases, CasesBulkGetRequest, CasesBulkGetResponse } from './api'; - -export type { - CaseUI, - CasesUI, - CasesFindResponseUI, - Ecs, - CasesFeatures, - CaseViewRefreshPropInterface, - CasesPermissions, -} from './ui/types'; - export { StatusAll } from './ui/types'; - -export { getCreateConnectorUrl, getAllConnectorsUrl } from './utils/connectors_api'; export { createUICapabilities } from './utils/capabilities'; export { getApiTags } from './utils/api_tags'; diff --git a/x-pack/plugins/cases/public/client/api/index.ts b/x-pack/plugins/cases/public/client/api/index.ts index 46f977643c25c..86a0643cddd09 100644 --- a/x-pack/plugins/cases/public/client/api/index.ts +++ b/x-pack/plugins/cases/public/client/api/index.ts @@ -14,8 +14,8 @@ import type { CasesMetricsRequest, } from '../../../common/api'; import { getCasesFromAlertsUrl } from '../../../common/api'; -import type { CasesFindResponseUI, CasesStatus, CasesMetrics } from '../../../common/ui'; import { bulkGetCases, getCases, getCasesMetrics, getCasesStatus } from '../../api'; +import type { CasesFindResponseUI, CasesStatus, CasesMetrics } from '../../../common/ui'; import type { CasesUiStart } from '../../types'; export const createClientAPI = ({ http }: { http: HttpStart }): CasesUiStart['api'] => { diff --git a/x-pack/plugins/cases/public/common/navigation/hooks.ts b/x-pack/plugins/cases/public/common/navigation/hooks.ts index 81b59c9b42637..5d8b835defdc5 100644 --- a/x-pack/plugins/cases/public/common/navigation/hooks.ts +++ b/x-pack/plugins/cases/public/common/navigation/hooks.ts @@ -8,25 +8,26 @@ import { useCallback, useEffect, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { parse, stringify } from 'query-string'; import { APP_ID, CASES_CONFIGURE_PATH, CASES_CREATE_PATH } from '../../../common/constants'; import { useNavigation } from '../lib/kibana'; import { useCasesContext } from '../../components/cases_context/use_cases_context'; import type { ICasesDeepLinkId } from './deep_links'; import type { CaseViewPathParams, CaseViewPathSearchParams } from './paths'; import { generateCaseViewPath } from './paths'; +import { stringifyToURL, parseURL } from '../../components/utils'; export const useCaseViewParams = () => useParams(); export function useUrlParams() { const { search } = useLocation(); - const [urlParams, setUrlParams] = useState(() => parse(search)); + const [urlParams, setUrlParams] = useState(() => parseURL(search)); const toUrlParams = useCallback( - (params: CaseViewPathSearchParams = urlParams) => stringify(params), + (params: CaseViewPathSearchParams = urlParams) => + stringifyToURL(params as Record), [urlParams] ); useEffect(() => { - setUrlParams(parse(search)); + setUrlParams(parseURL(search)); }, [search]); return { urlParams, diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx index 06a45ee6b5ea3..58ba9127566a0 100644 --- a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import type { EuiSuperSelectOption } from '@elastic/eui'; import { EuiSuperSelect, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { Status } from '@kbn/cases-components'; +import { Status } from '@kbn/cases-components/src/status/status'; import { allCaseStatus, statuses } from '../status'; import type { CaseStatusWithAllStatus } from '../../../common/ui/types'; import { StatusAll } from '../../../common/ui/types'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx index 66366892cd4db..c07b35b424e71 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx @@ -16,10 +16,10 @@ import { getFilterOptionsLocalStorageKey, } from './use_all_cases_state'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from '../../containers/use_get_cases'; -import { stringify } from 'query-string'; import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../../containers/constants'; import { CaseStatuses } from '../../../common'; import { SortFieldCase } from '../../containers/types'; +import { stringifyToURL } from '../utils'; const LOCAL_STORAGE_QUERY_PARAMS_DEFAULTS = { perPage: DEFAULT_QUERY_PARAMS.perPage, @@ -160,29 +160,28 @@ describe('useAllCasesQueryParams', () => { }; const expectedUrl = { ...URL_DEFAULTS, ...nonDefaultUrlParams }; - mockLocation.search = stringify(nonDefaultUrlParams); + mockLocation.search = stringifyToURL(nonDefaultUrlParams as unknown as Record); renderHook(() => useAllCasesState(), { wrapper: ({ children }) => {children}, }); expect(useHistory().replace).toHaveBeenCalledWith({ - search: stringify(expectedUrl), + search: stringifyToURL(expectedUrl as unknown as Record), }); }); it('takes into account existing url filter options on first run', () => { const nonDefaultUrlParams = { severity: 'critical', status: 'open' }; - const expectedUrl = { ...URL_DEFAULTS, ...nonDefaultUrlParams }; - mockLocation.search = stringify(nonDefaultUrlParams); + mockLocation.search = stringifyToURL(nonDefaultUrlParams); renderHook(() => useAllCasesState(), { wrapper: ({ children }) => {children}, }); expect(useHistory().replace).toHaveBeenCalledWith({ - search: stringify(expectedUrl), + search: 'severity=critical&status=open&page=1&perPage=10&sortField=createdAt&sortOrder=desc', }); }); @@ -190,16 +189,16 @@ describe('useAllCasesQueryParams', () => { const nonDefaultUrlParams = { foo: 'bar', }; - const expectedUrl = { ...URL_DEFAULTS, ...nonDefaultUrlParams }; - mockLocation.search = stringify(nonDefaultUrlParams); + mockLocation.search = stringifyToURL(nonDefaultUrlParams); renderHook(() => useAllCasesState(), { wrapper: ({ children }) => {children}, }); expect(useHistory().replace).toHaveBeenCalledWith({ - search: stringify(expectedUrl), + search: + 'foo=bar&page=1&perPage=10&sortField=createdAt&sortOrder=desc&severity=all&status=all', }); }); @@ -208,7 +207,7 @@ describe('useAllCasesQueryParams', () => { perPage: DEFAULT_TABLE_LIMIT + 5, }; - mockLocation.search = stringify(nonDefaultUrlParams); + mockLocation.search = stringifyToURL(nonDefaultUrlParams as unknown as Record); localStorage.setItem( LOCALSTORAGE_QUERY_PARAMS_KEY, @@ -231,7 +230,7 @@ describe('useAllCasesQueryParams', () => { status: 'open', }; - mockLocation.search = stringify(nonDefaultUrlParams); + mockLocation.search = stringifyToURL(nonDefaultUrlParams); localStorage.setItem( LOCALSTORAGE_FILTER_OPTIONS_KEY, @@ -260,14 +259,14 @@ describe('useAllCasesQueryParams', () => { }); it('url perPage query param cannot be > 100', () => { - mockLocation.search = stringify({ perPage: 1000 }); + mockLocation.search = stringifyToURL({ perPage: '1000' }); renderHook(() => useAllCasesState(), { wrapper: ({ children }) => {children}, }); expect(useHistory().replace).toHaveBeenCalledWith({ - search: stringify({ ...URL_DEFAULTS, perPage: 100 }), + search: 'perPage=100&page=1&sortField=createdAt&sortOrder=desc&severity=all&status=all', }); mockLocation.search = ''; @@ -286,14 +285,14 @@ describe('useAllCasesQueryParams', () => { }); it('validate spelling of url sortOrder', () => { - mockLocation.search = stringify({ sortOrder: 'foobar' }); + mockLocation.search = stringifyToURL({ sortOrder: 'foobar' }); renderHook(() => useAllCasesState(), { wrapper: ({ children }) => {children}, }); expect(useHistory().replace).toHaveBeenCalledWith({ - search: stringify({ ...URL_DEFAULTS }), + search: 'sortOrder=desc&page=1&perPage=10&sortField=createdAt&severity=all&status=all', }); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.tsx b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.tsx index 7bd87d6b3dd2a..69572abf94556 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.tsx @@ -10,7 +10,6 @@ import { useLocation, useHistory } from 'react-router-dom'; import { isEqual } from 'lodash'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { parse, stringify } from 'query-string'; import type { FilterOptions, @@ -23,6 +22,7 @@ import type { import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from '../../containers/use_get_cases'; import { parseUrlQueryParams } from './utils'; +import { stringifyToURL, parseURL } from '../utils'; import { LOCAL_STORAGE_KEYS } from '../../../common/constants'; import { SORT_ORDER_VALUES } from '../../../common/ui/types'; import { useCasesContext } from '../cases_context/use_cases_context'; @@ -140,7 +140,7 @@ export function useAllCasesState( return; } - const parsedUrlParams: ParsedUrlQueryParams = parse(location.search); + const parsedUrlParams: ParsedUrlQueryParams = parseURL(location.search); const urlParams: PartialQueryParams = parseUrlQueryParams(parsedUrlParams); let newQueryParams: QueryParams = getQueryParams(params, urlParams, localStorageQueryParams); @@ -168,7 +168,7 @@ export function useAllCasesState( const newFilterOptions: FilterOptions = getFilterOptions( filterOptions, params, - parse(location.search), + parseURL(location.search), localStorageFilterOptions ); @@ -192,7 +192,7 @@ export function useAllCasesState( ); const updateLocation = useCallback(() => { - const parsedUrlParams = parse(location.search); + const parsedUrlParams = parseURL(location.search); const stateUrlParams = { ...parsedUrlParams, ...queryParams, @@ -205,7 +205,10 @@ export function useAllCasesState( try { const newHistory = { ...location, - search: stringify({ ...parsedUrlParams, ...stateUrlParams }), + search: stringifyToURL({ ...parsedUrlParams, ...stateUrlParams } as unknown as Record< + string, + string + >), }; history.replace(newHistory); } catch { diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx index 29ff166b0d3d4..21507522a6e2e 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx @@ -22,7 +22,7 @@ import { } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; -import { Status } from '@kbn/cases-components'; +import { Status } from '@kbn/cases-components/src/status/status'; import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx index ab1c728ad6f35..9f0270ea85afd 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx @@ -7,7 +7,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; -import { Status } from '@kbn/cases-components'; +import { Status } from '@kbn/cases-components/src/status/status'; import type { CaseStatuses } from '../../../common/api'; import { caseStatuses } from '../../../common/api'; import { StatusPopoverButton } from '../status'; diff --git a/x-pack/plugins/cases/public/components/files/file_attachment_event.test.tsx b/x-pack/plugins/cases/public/components/files/file_attachment_event.test.tsx new file mode 100644 index 0000000000000..50a816138e3c0 --- /dev/null +++ b/x-pack/plugins/cases/public/components/files/file_attachment_event.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import type { AppMockRenderer } from '../../common/mock'; +import type { DownloadableFile } from './types'; + +import { createAppMockRenderer } from '../../common/mock'; +import { basicFileMock } from '../../containers/mock'; +import { FileAttachmentEvent } from './file_attachment_event'; + +describe('FileAttachmentEvent', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders clickable name', async () => { + appMockRender.render( + + ); + + const nameLink = await screen.findByTestId('cases-files-name-link'); + + expect(nameLink).toBeInTheDocument(); + + userEvent.click(nameLink); + + expect(await screen.findByTestId('cases-files-image-preview')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/files/file_attachment_event.tsx b/x-pack/plugins/cases/public/components/files/file_attachment_event.tsx new file mode 100644 index 0000000000000..443ab1c612bfb --- /dev/null +++ b/x-pack/plugins/cases/public/components/files/file_attachment_event.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +import type { DownloadableFile } from './types'; + +import { FileNameLink } from './file_name_link'; +import { FilePreview } from './file_preview'; +import * as i18n from './translations'; +import { useFilePreview } from './use_file_preview'; + +export const FileAttachmentEvent = ({ file }: { file: DownloadableFile }) => { + const { isPreviewVisible, showPreview, closePreview } = useFilePreview(); + + return ( + <> + {i18n.ADDED} + + {isPreviewVisible && } + + ); +}; + +FileAttachmentEvent.displayName = 'FileAttachmentEvent'; diff --git a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx index 39c62322dedeb..879f28e4f8922 100644 --- a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx @@ -7,12 +7,13 @@ import React from 'react'; import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../common/mock'; + import { createAppMockRenderer } from '../../common/mock'; -import userEvent from '@testing-library/user-event'; -import { FileNameLink } from './file_name_link'; import { basicFileMock } from '../../containers/mock'; +import { FileNameLink } from './file_name_link'; describe('FileNameLink', () => { let appMockRender: AppMockRenderer; diff --git a/x-pack/plugins/cases/public/components/files/file_type.tsx b/x-pack/plugins/cases/public/components/files/file_type.tsx index f8b434d984861..6908b9baead97 100644 --- a/x-pack/plugins/cases/public/components/files/file_type.tsx +++ b/x-pack/plugins/cases/public/components/files/file_type.tsx @@ -4,48 +4,43 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { Suspense, lazy } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import type { ExternalReferenceAttachmentType, ExternalReferenceAttachmentViewProps, } from '../../client/attachment_framework/types'; -import type { DownloadableFile } from './types'; import { AttachmentActionType } from '../../client/attachment_framework/types'; import { FILE_ATTACHMENT_TYPE } from '../../../common/api'; -import { FileDownloadButton } from './file_download_button'; -import { FileNameLink } from './file_name_link'; -import { FilePreview } from './file_preview'; import * as i18n from './translations'; import { isImage, isValidFileExternalReferenceMetadata } from './utils'; -import { useFilePreview } from './use_file_preview'; -import { FileDeleteButton } from './file_delete_button'; -interface FileAttachmentEventProps { - file: DownloadableFile; -} - -const FileAttachmentEvent = ({ file }: FileAttachmentEventProps) => { - const { isPreviewVisible, showPreview, closePreview } = useFilePreview(); +const FileAttachmentEvent = lazy(() => + import('./file_attachment_event').then((module) => ({ default: module.FileAttachmentEvent })) +); +const FileDeleteButton = lazy(() => + import('./file_delete_button').then((module) => ({ default: module.FileDeleteButton })) +); +const FileDownloadButton = lazy(() => + import('./file_download_button').then((module) => ({ default: module.FileDownloadButton })) +); +function getFileDownloadButton(fileId: string) { return ( - <> - {i18n.ADDED} - - {isPreviewVisible && } - + }> + + ); -}; - -FileAttachmentEvent.displayName = 'FileAttachmentEvent'; - -function getFileDownloadButton(fileId: string) { - return ; } function getFileDeleteButton(caseId: string, fileId: string) { - return ; + return ( + }> + + + ); } const getFileAttachmentActions = ({ caseId, fileId }: { caseId: string; fileId: string }) => [ @@ -91,7 +86,11 @@ const getFileAttachmentViewObject = (props: ExternalReferenceAttachmentViewProps }; return { - event: , + event: ( + }> + + + ), timelineAvatar: isImage(file) ? 'image' : 'document', getActions: () => getFileAttachmentActions({ caseId, fileId }), hideDefaultActions: true, diff --git a/x-pack/plugins/cases/public/components/files/utils.test.tsx b/x-pack/plugins/cases/public/components/files/utils.test.tsx index 411492d1a2bab..a8cea79a34396 100644 --- a/x-pack/plugins/cases/public/components/files/utils.test.tsx +++ b/x-pack/plugins/cases/public/components/files/utils.test.tsx @@ -8,13 +8,15 @@ import type { JsonValue } from '@kbn/utility-types'; import { compressionMimeTypes, - imageMimeTypes, + IMAGE_MIME_TYPES, pdfMimeTypes, textMimeTypes, } from '../../../common/constants/mime_types'; import { basicFileMock } from '../../containers/mock'; import { isImage, isValidFileExternalReferenceMetadata, parseMimeType } from './utils'; +const imageMimeTypes = Array.from(IMAGE_MIME_TYPES); + describe('isImage', () => { it.each(imageMimeTypes)('should return true for image mime type: %s', (mimeType) => { expect(isImage({ mimeType })).toBeTruthy(); diff --git a/x-pack/plugins/cases/public/components/files/utils.tsx b/x-pack/plugins/cases/public/components/files/utils.tsx index b870c733eb10e..7f7ed09f29874 100644 --- a/x-pack/plugins/cases/public/components/files/utils.tsx +++ b/x-pack/plugins/cases/public/components/files/utils.tsx @@ -12,7 +12,7 @@ import type { import { compressionMimeTypes, - imageMimeTypes, + IMAGE_MIME_TYPES, textMimeTypes, pdfMimeTypes, } from '../../../common/constants/mime_types'; @@ -26,7 +26,7 @@ export const parseMimeType = (mimeType: string | undefined) => { return i18n.UNKNOWN_MIME_TYPE; } - if (imageMimeTypes.includes(mimeType)) { + if (IMAGE_MIME_TYPES.has(mimeType)) { return i18n.IMAGE_MIME_TYPE; } diff --git a/x-pack/plugins/cases/public/components/property_actions/index.tsx b/x-pack/plugins/cases/public/components/property_actions/index.tsx index 833ace8333d2b..443cab88760b9 100644 --- a/x-pack/plugins/cases/public/components/property_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/property_actions/index.tsx @@ -5,9 +5,16 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { Suspense, useCallback, useState } from 'react'; import type { EuiButtonProps } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem, EuiPopover, EuiButtonIcon, EuiButtonEmpty } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiButtonIcon, + EuiButtonEmpty, + EuiLoadingSpinner, +} from '@elastic/eui'; import type { AttachmentAction } from '../../client/attachment_framework/types'; @@ -106,7 +113,9 @@ export const PropertyActions = React.memo( customDataTestSubj={customDataTestSubj} /> )) || - (action.type === AttachmentActionType.CUSTOM && action.render())} + (action.type === AttachmentActionType.CUSTOM && ( + }>{action.render()} + ))} ))} diff --git a/x-pack/plugins/cases/public/components/status/config.ts b/x-pack/plugins/cases/public/components/status/config.ts index 113f91d5194df..f1e9a97144d73 100644 --- a/x-pack/plugins/cases/public/components/status/config.ts +++ b/x-pack/plugins/cases/public/components/status/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getStatusConfiguration } from '@kbn/cases-components'; +import { getStatusConfiguration } from '@kbn/cases-components/src/status/config'; import { StatusAll } from '../../../common/ui/types'; import { CaseStatuses } from '../../../common/api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/status/status_popover_button.tsx b/x-pack/plugins/cases/public/components/status/status_popover_button.tsx index 04a426347c83f..e663bf9865685 100644 --- a/x-pack/plugins/cases/public/components/status/status_popover_button.tsx +++ b/x-pack/plugins/cases/public/components/status/status_popover_button.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { EuiBadge } from '@elastic/eui'; -import type { CaseStatuses } from '@kbn/cases-components'; +import type { CaseStatuses } from '@kbn/cases-components/src/status/types'; import { statuses } from './config'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/user_actions/status.tsx b/x-pack/plugins/cases/public/components/user_actions/status.tsx index 936999fbffe31..d81aec74083b8 100644 --- a/x-pack/plugins/cases/public/components/user_actions/status.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/status.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Status } from '@kbn/cases-components'; +import { Status } from '@kbn/cases-components/src/status/status'; import type { CaseStatuses, StatusUserAction } from '../../../common/api'; import type { UserActionBuilder, UserActionResponse } from './types'; import { createCommonUpdateUserActionBuilder } from './common'; diff --git a/x-pack/plugins/cases/public/components/utils.test.ts b/x-pack/plugins/cases/public/components/utils.test.ts index beaaf789b4b4c..3866b2503d63f 100644 --- a/x-pack/plugins/cases/public/components/utils.test.ts +++ b/x-pack/plugins/cases/public/components/utils.test.ts @@ -16,6 +16,8 @@ import { isDeprecatedConnector, isEmptyValue, removeItemFromSessionStorage, + parseURL, + stringifyToURL, } from './utils'; describe('Utils', () => { @@ -212,4 +214,44 @@ describe('Utils', () => { expect(isEmptyValue(value)).toBe(false); }); }); + + describe('parseUrl', () => { + it('parses URL correctly into object', () => { + expect( + parseURL( + 'severity=critical&status=open&page=1&perPage=10&sortField=createdAt&sortOrder=desc' + ) + ).toEqual({ + page: '1', + severity: 'critical', + perPage: '10', + sortField: 'createdAt', + sortOrder: 'desc', + status: 'open', + }); + }); + + it('parses empty URL correctly into object', () => { + expect(parseURL('')).toEqual({}); + }); + }); + + describe('stringifyToURL', () => { + it('stringifies object correctly into URL', () => { + expect( + stringifyToURL({ + page: '1', + severity: 'critical', + perPage: '10', + sortField: 'createdAt', + sortOrder: 'desc', + status: 'open', + }) + ).toBe('page=1&severity=critical&perPage=10&sortField=createdAt&sortOrder=desc&status=open'); + }); + + it('stringifies empty object correctly into URL', () => { + expect(stringifyToURL({})).toBe(''); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts index a2e5f36b87760..ff1775b54a603 100644 --- a/x-pack/plugins/cases/public/components/utils.ts +++ b/x-pack/plugins/cases/public/components/utils.ts @@ -171,3 +171,8 @@ export const isDeprecatedConnector = (connector?: CaseActionConnector): boolean export const removeItemFromSessionStorage = (key: string) => { window.sessionStorage.removeItem(key); }; + +export const stringifyToURL = (parsedParams: Record) => + new URLSearchParams(parsedParams).toString(); +export const parseURL = (queryString: string) => + Object.fromEntries(new URLSearchParams(queryString)); diff --git a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx index 8f6c0effb7b2c..374151614b1f1 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.tsx @@ -39,5 +39,3 @@ export const useDeleteFileAttachment = () => { } ); }; - -export type UseDeleteFileAttachment = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_get_case_file_stats.tsx b/x-pack/plugins/cases/public/containers/use_get_case_file_stats.tsx index dd444a5bbb5e9..a6c3e2212eb03 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_file_stats.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_file_stats.tsx @@ -25,7 +25,7 @@ const getTotalFromFileList = (data: { files: FileJSON[]; total: number }): { tot total: data.total, }); -export interface GetCaseFileStatsParams { +interface GetCaseFileStatsParams { caseId: string; } diff --git a/x-pack/plugins/cases/public/index.tsx b/x-pack/plugins/cases/public/index.tsx index 7130d412bebf1..365c850909116 100644 --- a/x-pack/plugins/cases/public/index.tsx +++ b/x-pack/plugins/cases/public/index.tsx @@ -14,25 +14,12 @@ export function plugin(initializerContext: PluginInitializerContext) { export { DRAFT_COMMENT_STORAGE_ID } from './components/markdown_editor/plugins/lens/constants'; -export type { CasesUiPlugin }; export type { CasesUiStart, CasesUiSetup } from './types'; -export type { GetCasesProps } from './client/ui/get_cases'; export type { GetCreateCaseFlyoutProps } from './client/ui/get_create_case_flyout'; export type { GetAllCasesSelectorModalProps } from './client/ui/get_all_cases_selector_modal'; export type { GetRecentCasesProps } from './client/ui/get_recent_cases'; -export type { - CaseAttachments, - SupportedCaseAttachment, - CaseAttachmentsWithoutOwner, -} from './types'; +export type { CaseAttachments, CaseAttachmentsWithoutOwner } from './types'; export type { ICasesDeepLinkId } from './common/navigation'; -export { - getCasesDeepLinks, - CasesDeepLinkId, - generateCaseViewPath, - getCreateCasePath, - getCaseViewPath, - getCasesConfigurePath, -} from './common/navigation'; +export { getCasesDeepLinks, CasesDeepLinkId, generateCaseViewPath } from './common/navigation'; diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index 2c4a653254195..e429e6461e72e 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -22,7 +22,6 @@ const uiMock: jest.Mocked = { getCases: jest.fn(), getCasesContext: jest.fn().mockImplementation(() => mockCasesContext), getAllCasesSelectorModal: jest.fn(), - getCreateCaseFlyout: jest.fn(), getRecentCases: jest.fn(), }; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 1f9d3edf35776..ee07fc217f2c1 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -22,7 +22,6 @@ import { getRuleIdFromEvent } from './client/helpers/get_rule_id_from_event'; import { getAllCasesSelectorModalLazy } from './client/ui/get_all_cases_selector_modal'; import { getCasesLazy } from './client/ui/get_cases'; import { getCasesContextLazy } from './client/ui/get_cases_context'; -import { getCreateCaseFlyoutLazy } from './client/ui/get_create_case_flyout'; import { getRecentCasesLazy } from './client/ui/get_recent_cases'; import { groupAlertsByRule } from './client/helpers/group_alerts_by_rule'; import { getUICapabilities } from './client/helpers/capabilities'; @@ -159,14 +158,6 @@ export class CasesUiPlugin persistableStateAttachmentTypeRegistry: this.persistableStateAttachmentTypeRegistry, getFilesClient: plugins.files.filesClientFactory.asScoped, }), - // @deprecated Please use the hook useCasesAddToNewCaseFlyout - getCreateCaseFlyout: (props) => - getCreateCaseFlyoutLazy({ - ...props, - externalReferenceAttachmentTypeRegistry: this.externalReferenceAttachmentTypeRegistry, - persistableStateAttachmentTypeRegistry: this.persistableStateAttachmentTypeRegistry, - getFilesClient: plugins.files.filesClientFactory.asScoped, - }), // @deprecated Please use the hook useCasesAddToExistingCaseModal getAllCasesSelectorModal: (props) => getAllCasesSelectorModalLazy({ diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 4f401dce1df45..6206a3304c88d 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -47,7 +47,6 @@ import type { getRuleIdFromEvent } from './client/helpers/get_rule_id_from_event import type { GetCasesContextProps } from './client/ui/get_cases_context'; import type { GetCasesProps } from './client/ui/get_cases'; import type { GetAllCasesSelectorModalProps } from './client/ui/get_all_cases_selector_modal'; -import type { GetCreateCaseFlyoutProps } from './client/ui/get_create_case_flyout'; import type { GetRecentCasesProps } from './client/ui/get_recent_cases'; import type { CasesStatus, CasesMetrics, CasesFindResponseUI } from '../common/ui'; import type { GroupAlertsByRule } from './client/helpers/group_alerts_by_rule'; @@ -128,14 +127,6 @@ export interface CasesUiStart { getAllCasesSelectorModal: ( props: GetAllCasesSelectorModalProps ) => ReactElement; - /** - * Flyout with the form to create a case for the owner - * @param props GetCreateCaseFlyoutProps - * @returns A react component that is a flyout for creating a case - */ - getCreateCaseFlyout: ( - props: GetCreateCaseFlyoutProps - ) => ReactElement; /** * Get the recent cases component * @param props GetRecentCasesProps diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts index 39cb16664411e..fd6c59c837516 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts @@ -32,6 +32,13 @@ import type { MigrateFunction, MigrateFunctionsObject } from '@kbn/kibana-utils- import type { SerializableRecord } from '@kbn/utility-types'; import { GENERATED_ALERT, SUB_CASE_SAVED_OBJECT } from './constants'; import { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; +import { omit, partition } from 'lodash'; +import type { + SavedObjectMigrationFn, + SavedObjectMigrationMap, + SavedObjectMigrationParams, +} from '@kbn/core-saved-objects-server'; +import { gte } from 'semver'; describe('comments migrations', () => { const contextMock = savedObjectsServiceMock.createMigrationContext(); @@ -108,7 +115,7 @@ describe('comments migrations', () => { jest.clearAllMocks(); }); - describe('lens embeddable migrations for by value panels', () => { + describe('lens migrations', () => { describe('7.14.0 remove time zone from Lens visualization date histogram', () => { const expectedLensVisualizationMigrated = { title: 'MyRenamedOps', @@ -271,6 +278,57 @@ describe('comments migrations', () => { expect((columns[2] as { params: {} }).params).toEqual({ timeZone: 'do not delete' }); }); }); + + it('should marked lens migrations as deferred correctly', () => { + const lensEmbeddableFactory = makeLensEmbeddableFactory( + () => ({}), + () => ({}), + {} + ); + + const lensMigrations = lensEmbeddableFactory().migrations; + const lensMigrationObject = + typeof lensMigrations === 'function' ? lensMigrations() : lensMigrations || {}; + const lensMigrationObjectWithFakeMigration = { + ...lensMigrationObject, + '8.9.0': jest.fn(), + '8.10.0': jest.fn(), + }; + + const lensVersions = Object.keys(lensMigrationObjectWithFakeMigration); + const [lensVersionToBeDeferred, lensVersionToNotBeDeferred] = partition( + lensVersions, + (version) => gte(version, '8.9.0') + ); + + const migrations = createCommentsMigrations({ + persistableStateAttachmentTypeRegistry: new PersistableStateAttachmentTypeRegistry(), + lensEmbeddableFactory: () => ({ + id: 'test', + migrations: lensMigrationObjectWithFakeMigration, + }), + }); + + for (const version of lensVersionToBeDeferred) { + const migration = migrations[version] as SavedObjectMigrationParams; + expect(migration.deferred).toBe(true); + expect(migration.transform).toEqual(expect.any(Function)); + } + + for (const version of lensVersionToNotBeDeferred) { + const migration = migrations[version] as SavedObjectMigrationParams; + expect(migration.deferred).toBe(false); + expect(migration.transform).toEqual(expect.any(Function)); + } + + const migrationsWithoutLens = omit(migrations, lensVersions); + + for (const version of Object.keys(migrationsWithoutLens)) { + const migration = migrationsWithoutLens[version] as SavedObjectMigrationFn; + + expect(migration).toEqual(expect.any(Function)); + } + }); }); describe('handles errors', () => { @@ -298,9 +356,9 @@ describe('comments migrations', () => { }; it('logs an error when it fails to parse invalid json', () => { - const commentMigrationFunction = migrateByValueLensVisualizations(migrationFunction); + const commentMigrationFunction = migrateByValueLensVisualizations(migrationFunction, '0.0.0'); - const result = commentMigrationFunction(caseComment, contextMock); + const result = commentMigrationFunction.transform(caseComment, contextMock); // the comment should remain unchanged when there is an error expect(result.attributes.comment).toEqual(comment); @@ -322,7 +380,7 @@ describe('comments migrations', () => { describe('mergeSavedObjectMigrationMaps', () => { it('logs an error when the passed migration functions fails', () => { const migrationObj1 = { - '1.0.0': migrateByValueLensVisualizations(migrationFunction), + '1.0.0': migrateByValueLensVisualizations(migrationFunction, '1.0.0'), } as unknown as MigrateFunctionsObject; const migrationObj2 = { @@ -351,7 +409,7 @@ describe('comments migrations', () => { it('it does not log an error when the migration function does not use the context', () => { const migrationObj1 = { - '1.0.0': migrateByValueLensVisualizations(migrationFunction), + '1.0.0': migrateByValueLensVisualizations(migrationFunction, '1.0.0'), } as unknown as MigrateFunctionsObject; const migrationObj2 = { diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts index 9f874561ef084..41943f90da199 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts @@ -11,12 +11,12 @@ import type { MigrateFunction, MigrateFunctionsObject } from '@kbn/kibana-utils- import type { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, - SavedObjectMigrationFn, SavedObjectMigrationMap, SavedObjectMigrationContext, } from '@kbn/core/server'; import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; import type { LensServerPluginSetup } from '@kbn/lens-plugin/server'; +import type { SavedObjectMigrationParams } from '@kbn/core-saved-objects-server'; import { CommentType } from '../../../common/api'; import type { LensMarkdownNode, MarkdownNode } from '../../../common/utils/markdown_plugins/utils'; import { @@ -26,8 +26,8 @@ import { } from '../../../common/utils/markdown_plugins/utils'; import type { SanitizedCaseOwner } from '.'; import { addOwnerToSO } from '.'; -import { logError } from './utils'; -import { GENERATED_ALERT, SUB_CASE_SAVED_OBJECT } from './constants'; +import { isDeferredMigration, logError } from './utils'; +import { GENERATED_ALERT, MIN_DEFERRED_KIBANA_VERSION, SUB_CASE_SAVED_OBJECT } from './constants'; import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; interface UnsanitizedComment { @@ -63,8 +63,8 @@ export const createCommentsMigrations = ( const embeddableMigrations = mapValues< MigrateFunctionsObject, - SavedObjectMigrationFn<{ comment?: string }> - >(lensMigrationObject, migrateByValueLensVisualizations) as MigrateFunctionsObject; + SavedObjectMigrationParams<{ comment?: string }, { comment?: string }> + >(lensMigrationObject, migrateByValueLensVisualizations); const commentsMigrations = { '7.11.0': ( @@ -119,39 +119,54 @@ export const createCommentsMigrations = ( return mergeSavedObjectMigrationMaps(commentsMigrations, embeddableMigrations); }; -export const migrateByValueLensVisualizations = - (migrate: MigrateFunction): SavedObjectMigrationFn<{ comment?: string }, { comment?: string }> => - (doc: SavedObjectUnsanitizedDoc<{ comment?: string }>, context: SavedObjectMigrationContext) => { - if (doc.attributes.comment == null) { - return doc; - } +export const migrateByValueLensVisualizations = ( + migrate: MigrateFunction, + migrationVersion: string +): SavedObjectMigrationParams<{ comment?: string }, { comment?: string }> => { + const deferred = isDeferredMigration(MIN_DEFERRED_KIBANA_VERSION, migrationVersion); - try { - const parsedComment = parseCommentString(doc.attributes.comment); - const migratedComment = parsedComment.children.map((comment) => { - if (isLensMarkdownNode(comment)) { - // casting here because ts complains that comment isn't serializable because LensMarkdownNode - // extends Node which has fields that conflict with SerializableRecord even though it is serializable - return migrate(comment as SerializableRecord) as LensMarkdownNode; - } - - return comment; - }); - - const migratedMarkdown = { ...parsedComment, children: migratedComment }; + return { + // @ts-expect-error: remove when core changes the types + deferred, + transform: ( + doc: SavedObjectUnsanitizedDoc<{ comment?: string }>, + context: SavedObjectMigrationContext + ) => { + if (doc.attributes.comment == null) { + return doc; + } - return { - ...doc, - attributes: { - ...doc.attributes, - comment: stringifyCommentWithoutTrailingNewline(doc.attributes.comment, migratedMarkdown), - }, - }; - } catch (error) { - logError({ id: doc.id, context, error, docType: 'comment', docKey: 'comment' }); - return doc; - } + try { + const parsedComment = parseCommentString(doc.attributes.comment); + const migratedComment = parsedComment.children.map((comment) => { + if (isLensMarkdownNode(comment)) { + // casting here because ts complains that comment isn't serializable because LensMarkdownNode + // extends Node which has fields that conflict with SerializableRecord even though it is serializable + return migrate(comment as SerializableRecord) as LensMarkdownNode; + } + + return comment; + }); + + const migratedMarkdown = { ...parsedComment, children: migratedComment }; + + return { + ...doc, + attributes: { + ...doc.attributes, + comment: stringifyCommentWithoutTrailingNewline( + doc.attributes.comment, + migratedMarkdown + ), + }, + }; + } catch (error) { + logError({ id: doc.id, context, error, docType: 'comment', docKey: 'comment' }); + return doc; + } + }, }; +}; export const stringifyCommentWithoutTrailingNewline = ( originalComment: string, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/constants.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/constants.ts index 9f428af939a06..e08b0632419b3 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/constants.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/constants.ts @@ -12,3 +12,5 @@ export const GENERATED_ALERT = 'generated_alert'; export const COMMENT_ASSOCIATION_TYPE = 'case'; export const CASE_TYPE_INDIVIDUAL = 'individual'; export const SUB_CASE_SAVED_OBJECT = 'cases-sub-case'; + +export const MIN_DEFERRED_KIBANA_VERSION = '8.9.0'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.test.ts index a81cd40cb2c15..71e1a7397da3a 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.test.ts @@ -7,7 +7,8 @@ import type { SavedObjectsMigrationLogger } from '@kbn/core/server'; import { migrationMocks } from '@kbn/core/server/mocks'; -import { logError } from './utils'; +import { MIN_DEFERRED_KIBANA_VERSION } from './constants'; +import { isDeferredMigration, logError } from './utils'; describe('migration utils', () => { const context = migrationMocks.createContext(); @@ -16,18 +17,19 @@ describe('migration utils', () => { jest.clearAllMocks(); }); - it('logs an error', () => { - const log = context.log as jest.Mocked; + describe('logError', () => { + it('logs an error', () => { + const log = context.log as jest.Mocked; - logError({ - id: '1', - context, - error: new Error('an error'), - docType: 'a document', - docKey: 'key', - }); + logError({ + id: '1', + context, + error: new Error('an error'), + docType: 'a document', + docKey: 'key', + }); - expect(log.error.mock.calls[0]).toMatchInlineSnapshot(` + expect(log.error.mock.calls[0]).toMatchInlineSnapshot(` Array [ "Failed to migrate a document with doc id: 1 version: 8.0.0 error: an error", Object { @@ -39,5 +41,28 @@ describe('migration utils', () => { }, ] `); + }); + }); + + describe('isDeferredMigration', () => { + it('should mark the migration as deferred if the migration version is greater than the Kibana version', () => { + expect(isDeferredMigration(MIN_DEFERRED_KIBANA_VERSION, '8.10.0')).toBe(true); + }); + + it('should mark the migration as not deferred if the migration version is smaller than the Kibana version', () => { + expect(isDeferredMigration(MIN_DEFERRED_KIBANA_VERSION, '8.8.0')).toBe(false); + }); + + it('should mark the migration as deferred if the migration version is equal to the Kibana version', () => { + expect(isDeferredMigration(MIN_DEFERRED_KIBANA_VERSION, '8.9.0')).toBe(true); + }); + + it('should return false if the Kibana version is not a valid semver', () => { + expect(isDeferredMigration('invalid', '8.8.0')).toBe(false); + }); + + it('should return false if the migration version is not a valid semver', () => { + expect(isDeferredMigration(MIN_DEFERRED_KIBANA_VERSION, 'invalid')).toBe(false); + }); }); }); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts index 3b3a7c8e50792..a25111ef57af2 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts @@ -5,6 +5,9 @@ * 2.0. */ +import valid from 'semver/functions/valid'; +import gte from 'semver/functions/gte'; + import type { LogMeta, SavedObjectMigrationContext, @@ -50,3 +53,13 @@ export function pipeMigrations(...migrations: Array>): CaseM return (doc: SavedObjectUnsanitizedDoc) => migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc); } + +export const isDeferredMigration = ( + minDeferredKibanaVersion: string, + migrationVersion: string +): boolean => + Boolean( + valid(migrationVersion) && + valid(minDeferredKibanaVersion) && + gte(migrationVersion, minDeferredKibanaVersion) + ); diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx index ed0ccefbf7b13..21c50a7f293b6 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx @@ -49,13 +49,7 @@ export const FileContents: FC = ({ data, format, numberOfLines }) => { - + ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx index 4e3da71e83ebb..dc28c72cb324a 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx @@ -200,7 +200,6 @@ const IndexSettings: FC = ({ initialized, data, onChange }) => readOnly={initialized === true} value={data} height={EDITOR_HEIGHT} - syntaxChecking={false} onChange={onChange} /> @@ -225,7 +224,6 @@ const Mappings: FC = ({ initialized, data, onChange }) => { readOnly={initialized === true} value={data} height={EDITOR_HEIGHT} - syntaxChecking={false} onChange={onChange} /> @@ -250,7 +248,6 @@ const IngestPipeline: FC = ({ initialized, data, onChange }) => readOnly={initialized === true} value={data} height={EDITOR_HEIGHT} - syntaxChecking={false} onChange={onChange} /> diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/json_editor/json_editor.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/json_editor/json_editor.tsx index 5c28b4bb31088..1bed983561240 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/json_editor/json_editor.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/json_editor/json_editor.tsx @@ -5,14 +5,10 @@ * 2.0. */ +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import React, { FC } from 'react'; -import { - expandLiteralStrings, - XJsonMode, - EuiCodeEditor, - EuiCodeEditorProps, -} from '../../../shared_imports'; +import { expandLiteralStrings, XJsonMode, EuiCodeEditorProps } from '../../../shared_imports'; export const EDITOR_MODE = { TEXT: 'text', JSON: 'json', XJSON: new XJsonMode() }; @@ -22,18 +18,15 @@ interface JobEditorProps { width?: string; mode?: typeof EDITOR_MODE[keyof typeof EDITOR_MODE]; readOnly?: boolean; - syntaxChecking?: boolean; - theme?: string; onChange?: EuiCodeEditorProps['onChange']; } export const JsonEditor: FC = ({ value, height = '500px', - width = '100%', + // 99% width allows the editor to resize horizontally. 100% prevents it from resizing. + width = '99%', mode = EDITOR_MODE.JSON, readOnly = false, - syntaxChecking = true, - theme = 'textmate', onChange = () => {}, }) => { if (mode === EDITOR_MODE.XJSON) { @@ -41,20 +34,30 @@ export const JsonEditor: FC = ({ } return ( - diff --git a/x-pack/plugins/enterprise_search/common/types/connectors.ts b/x-pack/plugins/enterprise_search/common/types/connectors.ts index 1533ed46d298e..322982f9e3c51 100644 --- a/x-pack/plugins/enterprise_search/common/types/connectors.ts +++ b/x-pack/plugins/enterprise_search/common/types/connectors.ts @@ -61,7 +61,7 @@ export type ConnectorSyncConfiguration = Record | null; +export interface SchedulingConfiguraton { + access_control: ConnectorScheduling; + full: ConnectorScheduling; + incremental: ConnectorScheduling; +} + export interface Connector { api_key_id: string | null; configuration: ConnectorConfiguration; @@ -201,6 +207,7 @@ export interface Connector { index_name: string; is_native: boolean; language: string | null; + last_access_control_sync_error: string | null; last_access_control_sync_scheduled_at: string | null; last_access_control_sync_status: SyncStatus | null; last_incremental_sync_scheduled_at: string | null; @@ -211,10 +218,7 @@ export interface Connector { last_synced: string | null; name: string; pipeline?: IngestPipelineParams | null; - scheduling: { - enabled: boolean; - interval: string; // crontab syntax - }; + scheduling: SchedulingConfiguraton; service_type: string | null; status: ConnectorStatus; sync_now: boolean; diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/create_engine_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/create_engine_flyout.tsx index c147af6630efb..24223785f144f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/create_engine_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/create_engine_flyout.tsx @@ -129,10 +129,17 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/engines_list.tsx index e7219e1dce58d..fe16b13932ff3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/engines_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/engines/engines_list.tsx @@ -90,7 +90,7 @@ export const CreateEngineButton: React.FC = ({ disabled diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts index 0b0d73f5cbe99..4f92ffa6a7830 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts @@ -118,6 +118,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ index_name: 'connector', is_native: false, language: 'en', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: SyncStatus.COMPLETED, last_incremental_sync_scheduled_at: null, @@ -128,8 +129,18 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ last_synced: null, name: 'connector', scheduling: { - enabled: false, - interval: '', + access_control: { + enabled: false, + interval: '', + }, + full: { + enabled: false, + interval: '', + }, + incremental: { + enabled: false, + interval: '', + }, }, service_type: null, status: ConnectorStatus.CONFIGURED, @@ -233,6 +244,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ index_name: 'crawler', is_native: true, language: 'en', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: SyncStatus.COMPLETED, last_incremental_sync_scheduled_at: null, @@ -243,8 +255,18 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ last_synced: null, name: 'crawler', scheduling: { - enabled: false, - interval: '', + access_control: { + enabled: false, + interval: '', + }, + full: { + enabled: false, + interval: '', + }, + incremental: { + enabled: false, + interval: '', + }, }, service_type: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, status: ConnectorStatus.CONFIGURED, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts index 4d498db006a4d..f10ca7cfba3b0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts @@ -128,6 +128,7 @@ export const connectorIndex: ConnectorViewIndex = { index_name: 'connector', is_native: false, language: 'en', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: SyncStatus.COMPLETED, last_incremental_sync_scheduled_at: null, @@ -138,8 +139,18 @@ export const connectorIndex: ConnectorViewIndex = { last_synced: null, name: 'connector', scheduling: { - enabled: false, - interval: '', + access_control: { + enabled: false, + interval: '', + }, + full: { + enabled: false, + interval: '', + }, + incremental: { + enabled: false, + interval: '', + }, }, service_type: null, status: ConnectorStatus.CONFIGURED, @@ -247,6 +258,7 @@ export const crawlerIndex: CrawlerViewIndex = { index_name: 'crawler', is_native: true, language: 'en', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: SyncStatus.COMPLETED, last_incremental_sync_scheduled_at: null, @@ -257,8 +269,18 @@ export const crawlerIndex: CrawlerViewIndex = { last_synced: null, name: 'crawler', scheduling: { - enabled: false, - interval: '', + access_control: { + enabled: false, + interval: '', + }, + full: { + enabled: false, + interval: '', + }, + incremental: { + enabled: false, + interval: '', + }, }, service_type: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, status: ConnectorStatus.CONFIGURED, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_scheduling_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_scheduling_api_logic.ts index 2c68b5994e398..c2ffaa647614f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_scheduling_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_scheduling_api_logic.ts @@ -7,25 +7,25 @@ import { i18n } from '@kbn/i18n'; -import { ConnectorScheduling } from '../../../../../common/types/connectors'; +import { SchedulingConfiguraton } from '../../../../../common/types/connectors'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface UpdateConnectorSchedulingArgs { connectorId: string; - scheduling: ConnectorScheduling; + scheduling: SchedulingConfiguraton; } export const updateConnectorScheduling = async ({ connectorId, - scheduling: { enabled, interval }, + scheduling, }: UpdateConnectorSchedulingArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/scheduling`; await HttpLogic.values.http.post(route, { - body: JSON.stringify({ enabled, interval }), + body: JSON.stringify(scheduling), }); - return { enabled, interval }; + return scheduling; }; export const UpdateConnectorSchedulingApiLogic = createApiLogic( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx index 10edd3530ddca..632a3260ba9c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx @@ -47,7 +47,7 @@ export const MethodConnector: React.FC = ({ serviceType }) const isNative = Boolean(NATIVE_CONNECTORS.find((connector) => connector.serviceType === serviceType)) && - (isCloud || hasPlatinumLicense); + isCloud; const isBeta = Boolean( BETA_CONNECTORS.find((connector) => connector.serviceType === serviceType) ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx index 5623db01542b0..dc54b612e424f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiBadge, + EuiButtonIcon, EuiCheckableCard, EuiCheckableCardProps, EuiFlexGroup, @@ -24,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { BETA_LABEL, NATIVE_LABEL } from '../../../../shared/constants'; import './connector_checkable.scss'; +import { PlatinumLicensePopover } from '../../shared/platinum_license_popover/platinum_license_popover'; export type ConnectorCheckableProps = Omit< EuiCheckableCardProps, @@ -39,6 +41,7 @@ export type ConnectorCheckableProps = Omit< }; export const ConnectorCheckable: React.FC = ({ + disabled, documentationUrl, icon, isBeta, @@ -48,9 +51,11 @@ export const ConnectorCheckable: React.FC = ({ serviceType, ...props }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( = ({ )} - -

    {name}

    - + {disabled ? ( + +

    {name}

    +
    + ) : ( + +

    {name}

    +
    + )} + {disabled && ( + + setIsPopoverOpen(!isPopoverOpen)} + /> + } + closePopover={() => setIsPopoverOpen(false)} + isPopoverOpen={isPopoverOpen} + /> + + )} } name={name} @@ -93,21 +124,21 @@ export const ConnectorCheckable: React.FC = ({ > {showNativeBadge && ( - + {NATIVE_LABEL} )} {isBeta && ( - + {BETA_LABEL} )} {isTechPreview && ( - + {i18n.translate( 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.techPreviewLabel', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/select_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/select_connector.tsx index f40989454de13..fa3a025a66489 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/select_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/select_connector.tsx @@ -58,7 +58,7 @@ export const SelectConnector: React.FC = () => { const { isCloud } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); - const hasNativeAccess = isCloud || hasPlatinumLicense; + const hasNativeAccess = isCloud; return ( { {CONNECTORS.map((connector) => ( ), - status: index.connector.scheduling.enabled ? 'complete' : 'incomplete', + status: index.connector.scheduling.full.enabled ? 'complete' : 'incomplete', title: i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.title', { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx index 2feea44c703fb..97f130f1932ce 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx @@ -5,64 +5,77 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; -import { useActions, useValues } from 'kea'; +import { useValues } from 'kea'; import { + EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiText, - EuiSwitch, - EuiPanel, EuiSpacer, - EuiButton, - EuiButtonEmpty, - EuiCallOut, + EuiSplitPanel, + EuiText, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Status } from '../../../../../../common/types/api'; -import { ConnectorStatus } from '../../../../../../common/types/connectors'; -import { ConnectorIndex } from '../../../../../../common/types/indices'; -import { CronEditor } from '../../../../shared/cron_editor'; -import { Frequency } from '../../../../shared/cron_editor/types'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ConnectorStatus, SyncJobType } from '../../../../../../common/types/connectors'; + import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../../shared/kibana'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { UnsavedChangesPrompt } from '../../../../shared/unsaved_changes_prompt'; -import { UpdateConnectorSchedulingApiLogic } from '../../../api/connector/update_connector_scheduling_api_logic'; - import { SEARCH_INDEX_TAB_PATH } from '../../../routes'; import { IngestionStatus } from '../../../types'; -import { isConnectorIndex } from '../../../utils/indices'; - +import * as indices from '../../../utils/indices'; import { IndexViewLogic } from '../index_view_logic'; import { SearchIndexTabId } from '../search_index'; +import { ConnectorContentScheduling } from './connector_scheduling/full_content'; import { ConnectorSchedulingLogic } from './connector_scheduling_logic'; +interface SchedulePanelProps { + description: string; + title: string; +} +export const SchedulePanel: React.FC = ({ title, description, children }) => { + return ( + <> + + + +

    {title}

    +
    +
    + + + {description} + + + {children} + +
    + + ); +}; + export const ConnectorSchedulingComponent: React.FC = () => { - const { index, ingestionStatus } = useValues(IndexViewLogic); - const { status } = useValues(UpdateConnectorSchedulingApiLogic); - const { makeRequest } = useActions(UpdateConnectorSchedulingApiLogic); + const { productFeatures } = useValues(KibanaLogic); + const { ingestionStatus, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature } = + useValues(IndexViewLogic); + const { index } = useValues(IndexViewLogic); const { hasChanges } = useValues(ConnectorSchedulingLogic); - const { setHasChanges } = useActions(ConnectorSchedulingLogic); - - // Need to do this ugly casting because we can't check this after the below typecheck, because useState can't be used after an if - const schedulingInput = (index as ConnectorIndex)?.connector?.scheduling; - const [scheduling, setScheduling] = useState(schedulingInput); - const [fieldToPreferredValueMap, setFieldToPreferredValueMap] = useState({}); - const [simpleCron, setSimpleCron] = useState<{ - expression: string; - frequency: Frequency; - }>({ - expression: schedulingInput?.interval ?? '', - frequency: schedulingInput?.interval ? cronToFrequency(schedulingInput.interval) : 'HOUR', - }); - if (!isConnectorIndex(index)) { + const shouldShowIncrementalSync = + hasIncrementalSyncFeature && productFeatures.hasIncrementalSyncEnabled; + const shouldShowAccessControlSync = + hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled; + if (!indices.isConnectorIndex(index)) { return <>; } @@ -112,7 +125,6 @@ export const ConnectorSchedulingComponent: React.FC = () => { ); } - return ( <> { { defaultMessage: 'You have not saved your changes, are you sure you want to leave?' } )} /> - - - - {ingestionStatus === IngestionStatus.ERROR ? ( - + {ingestionStatus === IngestionStatus.ERROR ? ( + <> + + + + ) : ( + <> + )} + +

    + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.page.sync.label', + { + defaultMessage: 'Sync', + } + )} + + ), + }} + /> +

    +
    + + + + + + + + + {shouldShowIncrementalSync && ( + + + )} - /> - ) : ( - <> - )} + + + + {shouldShowAccessControlSync && ( - { - setScheduling({ ...scheduling, enabled: e.target.checked }); - setHasChanges(true); - }} - /> - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description', + description={i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.schedulePanel.documentLevelSecurity.description', { defaultMessage: - 'Your connector is configured and deployed. Configure a one-time sync by clicking the Sync button, or enable a recurring sync schedule. The connector uses UTC as its timezone. ', + 'Control the documents users can access, based on their permissions and roles. Schedule syncs to keep these access controls up to date.', } )} - - - - { - setSimpleCron({ - expression, - frequency, - }); - setFieldToPreferredValueMap(newFieldToPreferredValueMap); - setScheduling({ ...scheduling, interval: expression }); - setHasChanges(true); - }} - frequencyBlockList={['MINUTE']} - /> - - - - - { - setScheduling(schedulingInput); - setSimpleCron({ - expression: schedulingInput?.interval ?? '', - frequency: schedulingInput?.interval - ? cronToFrequency(schedulingInput.interval) - : 'HOUR', - }); - setHasChanges(false); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.connectorScheduling.resetButton.label', - { defaultMessage: 'Reset' } - )} - - - - makeRequest({ connectorId: index.connector.id, scheduling })} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.connectorScheduling.saveButton.label', - { defaultMessage: 'Save' } - )} - - - + > + + - -
    + )} + ); }; @@ -232,26 +225,3 @@ export interface Schedule { hours: string; minutes: string; } - -function cronToFrequency(cron: string): Frequency { - const fields = cron.split(' '); - if (fields.length < 4) { - return 'YEAR'; - } - if (fields[1] === '*') { - return 'MINUTE'; - } - if (fields[2] === '*') { - return 'HOUR'; - } - if (fields[3] === '*') { - return 'DAY'; - } - if (fields[4] === '?') { - return 'WEEK'; - } - if (fields[4] === '*') { - return 'MONTH'; - } - return 'YEAR'; -} 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 new file mode 100644 index 0000000000000..78cfb5459463f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/connector_cron_editor.tsx @@ -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 React, { useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiFlexItem, EuiFlexGroup, EuiButton, EuiButtonEmpty } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Status } from '../../../../../../../common/types/api'; +import { ConnectorScheduling, SyncJobType } from '../../../../../../../common/types/connectors'; +import { CronEditor } from '../../../../../shared/cron_editor'; +import { Frequency } from '../../../../../shared/cron_editor/types'; +import { UpdateConnectorSchedulingApiLogic } from '../../../../api/connector/update_connector_scheduling_api_logic'; +import { ConnectorSchedulingLogic } from '../connector_scheduling_logic'; + +interface ConnectorCronEditorProps { + onReset?(): void; + onSave?(interval: ConnectorScheduling['interval']): void; + scheduling: ConnectorScheduling; + type: SyncJobType; +} + +export const ConnectorCronEditor: React.FC = ({ + scheduling, + onSave, + onReset, + type, +}) => { + const { status } = useValues(UpdateConnectorSchedulingApiLogic); + const { hasFullSyncChanges, hasAccessSyncChanges, hasIncrementalSyncChanges } = + useValues(ConnectorSchedulingLogic); + const { clearHasChanges, setHasChanges } = useActions(ConnectorSchedulingLogic); + const [newInterval, setNewInterval] = useState(scheduling.interval); + const [fieldToPreferredValueMap, setFieldToPreferredValueMap] = useState({}); + const [simpleCron, setSimpleCron] = useState<{ + expression: string; + frequency: Frequency; + }>({ + expression: scheduling.interval ?? '', + frequency: scheduling.interval ? cronToFrequency(scheduling.interval) : 'HOUR', + }); + const hasChanges = + type === SyncJobType.FULL + ? hasFullSyncChanges + : type === SyncJobType.INCREMENTAL + ? hasIncrementalSyncChanges + : hasAccessSyncChanges; + + return ( + + + { + setSimpleCron({ + expression, + frequency, + }); + setFieldToPreferredValueMap(newFieldToPreferredValueMap); + setNewInterval(expression); + setHasChanges(type); + }} + frequencyBlockList={['MINUTE']} + /> + + + + + { + setNewInterval(scheduling.interval); + setSimpleCron({ + expression: scheduling.interval ?? '', + frequency: scheduling.interval ? cronToFrequency(scheduling.interval) : 'HOUR', + }); + clearHasChanges(type); + if (onReset) { + onReset(); + } + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.resetButton.label', + { defaultMessage: 'Reset' } + )} + + + + onSave && onSave(newInterval)} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.saveButton.label', + { defaultMessage: 'Save' } + )} + + + + + + ); +}; + +export interface Schedule { + days: string; + hours: string; + minutes: string; +} + +function cronToFrequency(cron: string): Frequency { + const fields = cron.split(' '); + if (fields.length < 4) { + return 'YEAR'; + } + if (fields[1] === '*') { + return 'MINUTE'; + } + if (fields[2] === '*') { + return 'HOUR'; + } + if (fields[3] === '*') { + return 'DAY'; + } + if (fields[4] === '?') { + return 'WEEK'; + } + if (fields[4] === '*') { + return 'MONTH'; + } + return 'YEAR'; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/full_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/full_content.tsx new file mode 100644 index 0000000000000..4bd336763dd92 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling/full_content.tsx @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { useActions } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSwitch, + EuiPanel, + EuiAccordion, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { SyncJobType } from '../../../../../../../common/types/connectors'; + +import { ConnectorViewIndex, CrawlerViewIndex } from '../../../../types'; + +import { ConnectorSchedulingLogic } from '../connector_scheduling_logic'; + +import { ConnectorCronEditor } from './connector_cron_editor'; + +export interface ConnectorContentSchedulingProps { + index: CrawlerViewIndex | ConnectorViewIndex; + type: SyncJobType; +} +const getAccordionTitle = (type: ConnectorContentSchedulingProps['type']) => { + switch (type) { + case SyncJobType.FULL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.fullSync.title', + { defaultMessage: 'Full content sync' } + ); + } + case SyncJobType.INCREMENTAL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.incrementalSync.title', + { defaultMessage: 'Incremental content sync' } + ); + } + case SyncJobType.ACCESS_CONTROL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.accessControlSync.title', + { defaultMessage: 'Access Control Sync' } + ); + } + } +}; +const getDescriptionText = (type: ConnectorContentSchedulingProps['type']) => { + switch (type) { + case SyncJobType.FULL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.fullSync.description', + { defaultMessage: 'Synchronize all data from your data source.' } + ); + } + case SyncJobType.INCREMENTAL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.incrementalSync.description', + { + defaultMessage: + 'A lightweight sync job that only fetches updated content from your data source.', + } + ); + } + case SyncJobType.ACCESS_CONTROL: { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.accessControlSync.description', + { defaultMessage: 'Schedule access control syncs to keep permissions mappings up to date.' } + ); + } + } +}; + +export const ConnectorContentScheduling: React.FC = ({ + type, + index, +}) => { + const { setHasChanges, updateScheduling } = useActions(ConnectorSchedulingLogic); + const schedulingInput = index.connector.scheduling; + const [scheduling, setScheduling] = useState(schedulingInput); + const [isAccordionOpen, setIsAccordionOpen] = useState<'open' | 'closed'>( + scheduling[type].enabled ? 'open' : 'closed' + ); + + return ( + <> + + + + +

    {getAccordionTitle(type)}

    +
    +
    + + +

    {getDescriptionText(type)}

    +
    +
    + + } + forceState={isAccordionOpen} + onToggle={(isOpen) => { + setIsAccordionOpen(isOpen ? 'open' : 'closed'); + }} + extraAction={ + { + if (e.target.checked) { + setIsAccordionOpen('open'); + } + setScheduling({ + ...scheduling, + ...{ + [type]: { enabled: e.target.checked, interval: scheduling[type].interval }, + }, + }); + setHasChanges(type); + }} + /> + } + > + + + { + setScheduling({ + ...schedulingInput, + }); + }} + onSave={(interval) => { + updateScheduling(type, { + connectorId: index.connector.id, + scheduling: { + ...index.connector.scheduling, + [type]: { + ...scheduling[type], + interval, + }, + }, + }); + }} + /> + + +
    +
    + + ); +}; + +export interface Schedule { + days: string; + hours: string; + minutes: string; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.test.ts index e2897a053a5b4..636035aa2ac83 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.test.ts @@ -7,14 +7,18 @@ import { LogicMounter } from '../../../../__mocks__/kea_logic'; -import { UpdateConnectorSchedulingApiLogic } from '../../../api/connector/update_connector_scheduling_api_logic'; +import { SyncJobType } from '../../../../../../common/types/connectors'; import { ConnectorSchedulingLogic } from './connector_scheduling_logic'; describe('ConnectorSchedulingLogic', () => { const { mount } = new LogicMounter(ConnectorSchedulingLogic); const DEFAULT_VALUES = { + hasAccessSyncChanges: false, hasChanges: false, + hasFullSyncChanges: false, + hasIncrementalSyncChanges: false, + makeRequestType: null, }; beforeEach(() => { @@ -26,23 +30,40 @@ describe('ConnectorSchedulingLogic', () => { }); describe('reducers', () => { - describe('hasChanges', () => { - it('should set false on apiSuccess', () => { - ConnectorSchedulingLogic.actions.setHasChanges(true); - UpdateConnectorSchedulingApiLogic.actions.apiSuccess({ - enabled: false, - interval: '', - }); - expect(ConnectorSchedulingLogic.values).toEqual({ - ...DEFAULT_VALUES, - hasChanges: false, + describe('hasFullSyncChanges', () => { + const expectedChanges = { + [SyncJobType.FULL]: { + hasFullSyncChanges: true, + }, + + [SyncJobType.INCREMENTAL]: { + hasIncrementalSyncChanges: true, + }, + + [SyncJobType.ACCESS_CONTROL]: { + hasAccessSyncChanges: true, + }, + }; + [SyncJobType.FULL, SyncJobType.INCREMENTAL, SyncJobType.ACCESS_CONTROL].forEach((type) => { + it(`sets related flag when setHasChanges called with ${type} `, () => { + ConnectorSchedulingLogic.actions.setHasChanges(type); + expect(ConnectorSchedulingLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasChanges: true, + ...expectedChanges[type], + }); }); - }); - it('should set hasChanges on setHasChanges', () => { - ConnectorSchedulingLogic.actions.setHasChanges(true); - expect(ConnectorSchedulingLogic.values).toEqual({ - ...DEFAULT_VALUES, - hasChanges: true, + it(`sets related flag false when clearHasChanges called with ${type}`, () => { + ConnectorSchedulingLogic.actions.setHasChanges(type); + expect(ConnectorSchedulingLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasChanges: true, + ...expectedChanges[type], + }); + ConnectorSchedulingLogic.actions.clearHasChanges(type); + expect(ConnectorSchedulingLogic.values).toEqual({ + ...DEFAULT_VALUES, + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.ts index 73a624351cbb8..fdab8b7c59aef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling_logic.ts @@ -7,7 +7,7 @@ import { kea, MakeLogicType } from 'kea'; -import { ConnectorScheduling } from '../../../../../../common/types/connectors'; +import { ConnectorScheduling, SyncJobType } from '../../../../../../common/types/connectors'; import { Actions } from '../../../../shared/api_logic/create_api_logic'; import { @@ -18,28 +18,91 @@ import { type ConnectorSchedulingActions = Pick< Actions, 'apiSuccess' -> & { setHasChanges: (hasChanges: boolean) => { hasChanges: boolean } }; +> & { + clearHasChanges: (type: SyncJobType) => { type: SyncJobType }; + makeRequest: typeof UpdateConnectorSchedulingApiLogic.actions.makeRequest; + setHasChanges: (type: SyncJobType) => { type: SyncJobType }; + updateScheduling: ( + type: SyncJobType, + payload: UpdateConnectorSchedulingArgs + ) => { payload: UpdateConnectorSchedulingArgs; type: SyncJobType }; +}; interface ConnectorSchedulingValues { + hasAccessSyncChanges: boolean; hasChanges: boolean; + hasFullSyncChanges: boolean; + hasIncrementalSyncChanges: boolean; + makeRequestType: SyncJobType | null; } export const ConnectorSchedulingLogic = kea< MakeLogicType >({ actions: { - setHasChanges: (hasChanges) => ({ hasChanges }), + clearHasChanges: (type) => ({ type }), + setHasChanges: (type) => ({ type }), + updateScheduling: (type, payload) => ({ payload, type }), }, connect: { - actions: [UpdateConnectorSchedulingApiLogic, ['apiSuccess']], + actions: [UpdateConnectorSchedulingApiLogic, ['apiSuccess', 'makeRequest']], }, + listeners: ({ actions, values }) => ({ + apiSuccess: () => { + if (values.makeRequestType) { + actions.clearHasChanges(values.makeRequestType); + } + }, + updateScheduling: ({ payload }) => { + actions.makeRequest(payload); + }, + }), reducers: { - hasChanges: [ + hasAccessSyncChanges: [ + false, + { + clearHasChanges: (current, { type }) => + type === SyncJobType.ACCESS_CONTROL ? false : current, + setHasChanges: (current, { type }) => + type === SyncJobType.ACCESS_CONTROL ? true : current, + }, + ], + hasFullSyncChanges: [ false, { - apiSuccess: () => false, - setHasChanges: (_, { hasChanges }) => hasChanges, + clearHasChanges: (current, { type }) => (type === SyncJobType.FULL ? false : current), + setHasChanges: (current, { type }) => (type === SyncJobType.FULL ? true : current), + }, + ], + hasIncrementalSyncChanges: [ + false, + { + clearHasChanges: (current, { type }) => + type === SyncJobType.INCREMENTAL ? false : current, + setHasChanges: (current, { type }) => (type === SyncJobType.INCREMENTAL ? true : current), + }, + ], + makeRequestType: [ + null, + { + updateScheduling: (_, { type }) => type, }, ], }, + selectors: ({ selectors }) => ({ + hasChanges: [ + () => [ + selectors.hasFullSyncChanges, + selectors.hasAccessSyncChanges, + selectors.hasIncrementalSyncChanges, + ], + ( + hasFullSyncChanges: boolean, + hasAccessSyncChanges: boolean, + hasIncrementalSyncChanges: boolean + ) => { + return hasFullSyncChanges || hasAccessSyncChanges || hasIncrementalSyncChanges; + }, + ], + }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts index baaeb70cb3afc..b2537b226b932 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -91,6 +91,7 @@ export const CONNECTORS_DICT: Record = { externalAuthDocsUrl: '', externalDocsUrl: '', icon: CONNECTOR_ICONS.sharepoint_online, + platinumOnly: true, }, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx index 450565088e842..d2e009baccc79 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx @@ -18,57 +18,24 @@ import { EuiText, EuiLink, EuiButton, - EuiConfirmModal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; - import { docLinks } from '../../../../../shared/doc_links'; +import { ConvertConnectorModal } from '../../../shared/convert_connector_modal/convert_connector_modal'; + import { ConvertConnectorLogic } from './convert_connector_logic'; export const ConvertConnector: React.FC = () => { - const { convertConnector, hideModal, showModal } = useActions(ConvertConnectorLogic); - const { isLoading, isModalVisible } = useValues(ConvertConnectorLogic); + const { showModal } = useActions(ConvertConnectorLogic); + const { isModalVisible } = useValues(ConvertConnectorLogic); return ( <> - {isModalVisible && ( - hideModal()} - onConfirm={() => convertConnector()} - title={i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.convertInfexConfirm.title', - { defaultMessage: 'Sure you want to convert your connector?' } - )} - buttonColor="danger" - cancelButtonText={CANCEL_BUTTON_LABEL} - confirmButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.text', - { - defaultMessage: 'Yes', - } - )} - isLoading={isLoading} - defaultFocusedButton="confirm" - maxWidth - > - -

    - {i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.description', - { - defaultMessage: - "Once you convert a native connector to a self-managed connector client this can't be undone.", - } - )} -

    -
    -
    - )} + {isModalVisible && } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx index 8b6a14b7b1252..798d59ccb7e3e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx @@ -62,7 +62,10 @@ export const NativeConnectorConfiguration: React.FC = () => { const hasDescription = !!index.connector.description; const hasConfigured = hasConfiguredConfiguration(index.connector.configuration); - const hasConfiguredAdvanced = index.connector.last_synced || index.connector.scheduling.enabled; + const hasConfiguredAdvanced = + index.connector.last_synced || + index.connector.scheduling.full.enabled || + index.connector.scheduling.incremental.enabled; const hasResearched = hasDescription || hasConfigured || hasConfiguredAdvanced; const icon = nativeConnector.icon; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts index 68d990650a175..a09a37ad1e93b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts @@ -12,6 +12,7 @@ export interface ConnectorClientSideDefinition { externalAuthDocsUrl?: string; externalDocsUrl: string; icon: string; + platinumOnly?: boolean; } export type ConnectorDefinition = ConnectorClientSideDefinition & ConnectorServerSideDefinition; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx index 3a4018776a9f7..bb909c80687ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx @@ -122,7 +122,7 @@ export const AutomaticCrawlScheduler: React.FC = () => { > submitConnectorSchedule({ ...newScheduling, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts index e2533e4e8f658..39fec142012fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts @@ -179,7 +179,7 @@ export const AutomaticCrawlSchedulerLogic = kea< actions.deleteCrawlSchedule(); } actions.submitConnectorSchedule({ - ...values.index.connector.scheduling, + ...values.index.connector.scheduling.full, enabled: values.crawlAutomatically && values.useConnectorSchedule, }); }, @@ -190,7 +190,10 @@ export const AutomaticCrawlSchedulerLogic = kea< submitConnectorSchedule: ({ scheduling }) => { actions.makeUpdateConnectorSchedulingRequest({ connectorId: values.index.connector.id, - scheduling, + scheduling: { + ...values.index.connector.scheduling, + full: scheduling, + }, }); }, submitCrawlSchedule: async () => { @@ -206,8 +209,8 @@ export const AutomaticCrawlSchedulerLogic = kea< `/internal/enterprise_search/indices/${indexName}/crawler/crawl_schedule`, { body: JSON.stringify({ - unit: values.crawlUnit, frequency: values.crawlFrequency, + unit: values.crawlUnit, use_connector_schedule: values.useConnectorSchedule, }), } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/overview.tsx index dc11821c94860..b280d3fbf36b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/overview.tsx @@ -7,15 +7,22 @@ import React from 'react'; -import { useValues } from 'kea'; +import { useActions, useValues } from 'kea'; -import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { docLinks } from '../../../shared/doc_links'; +import { KibanaLogic } from '../../../shared/kibana'; import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../utils/indices'; +import { ConvertConnectorModal } from '../shared/convert_connector_modal/convert_connector_modal'; + import { ApiTotalStats } from './api_total_stats'; +import { ConvertConnectorLogic } from './connector/native_connector_configuration/convert_connector_logic'; import { ConnectorTotalStats } from './connector_total_stats'; import { CrawlDetailsFlyout } from './crawler/crawl_details_flyout/crawl_details_flyout'; import { CrawlRequestsPanel } from './crawler/crawl_requests_panel/crawl_requests_panel'; @@ -28,6 +35,9 @@ import { SyncJobs } from './sync_jobs/sync_jobs'; export const SearchIndexOverview: React.FC = () => { const { indexData } = useValues(OverviewLogic); const { error } = useValues(IndexViewLogic); + const { isCloud } = useValues(KibanaLogic); + const { showModal } = useActions(ConvertConnectorLogic); + const { isModalVisible } = useValues(ConvertConnectorLogic); return ( <> @@ -50,6 +60,49 @@ export const SearchIndexOverview: React.FC = () => { )} + {isConnectorIndex(indexData) && indexData.connector.is_native && !isCloud && ( + <> + {isModalVisible && } + + + +

    + + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.nativeCloudCallout.connectorClient', + { defaultMessage: 'connector client' } + )} + + ), + }} + /> +

    +
    + + showModal()}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.searchIndex.convertConnector.buttonLabel', + { defaultMessage: 'Convert connector' } + )} + +
    + + + )} {isCrawlerIndex(indexData) ? ( ) : isConnectorIndex(indexData) ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/convert_connector_modal/convert_connector_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/convert_connector_modal/convert_connector_modal.tsx new file mode 100644 index 0000000000000..efaccd17bfed2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/convert_connector_modal/convert_connector_modal.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiConfirmModal, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; + +import { ConvertConnectorLogic } from '../../search_index/connector/native_connector_configuration/convert_connector_logic'; + +export const ConvertConnectorModal: React.FC = () => { + const { convertConnector, hideModal } = useActions(ConvertConnectorLogic); + const { isLoading } = useValues(ConvertConnectorLogic); + return ( + hideModal()} + onConfirm={() => convertConnector()} + title={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertInfexConfirm.title', + { defaultMessage: 'Sure you want to convert your connector?' } + )} + buttonColor="danger" + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.text', + { + defaultMessage: 'Yes', + } + )} + isLoading={isLoading} + defaultFocusedButton="confirm" + maxWidth + > + +

    + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.description', + { + defaultMessage: + "Once you convert a native connector to a self-managed connector client this can't be undone.", + } + )} +

    +
    +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/platinum_license_popover/platinum_license_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/platinum_license_popover/platinum_license_popover.tsx new file mode 100644 index 0000000000000..588c8bd0800ea --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/platinum_license_popover/platinum_license_popover.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { css } from '@emotion/react'; + +import { + EuiPopover, + EuiPopoverTitle, + EuiText, + EuiPopoverFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiPopoverProps, + useEuiTheme, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { docLinks } from '../../../../shared/doc_links'; +import { KibanaLogic } from '../../../../shared/kibana'; + +interface PlatinumLicensePopoverProps { + button: EuiPopoverProps['button']; + closePopover: () => void; + isPopoverOpen: boolean; +} + +export const PlatinumLicensePopover: React.FC = ({ + button, + isPopoverOpen, + closePopover, +}) => { + const { euiTheme } = useEuiTheme(); + return ( + + + {i18n.translate('xpack.enterpriseSearch.content.newIndex.selectConnector.upgradeTitle', { + defaultMessage: 'Upgrade to Elastic Platinum', + })} + + +

    + {i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.selectConnector.upgradeContent', + { + defaultMessage: + 'To use this connector, you must update your license to Platinum or start a 30-day free trial.', + } + )} +

    +
    + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.selectConnector.subscriptionButtonLabel', + { + defaultMessage: 'Subscription plans', + } + )} + + + + + KibanaLogic.values.navigateToUrl('/app/management/stack/license_management', { + shouldNotCreateHref: true, + }) + } + > + {i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.selectConnector.manageLicenseButtonLabel', + { + defaultMessage: 'Manage license', + } + )} + + + + +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx index 48b402436f69a..7b915f08c5df9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx @@ -9,14 +9,14 @@ import React, { useState } from 'react'; import { Frequency } from '@kbn/es-ui-shared-plugin/public/components/cron_editor/types'; -import { Connector } from '../../../../common/types/connectors'; +import { ConnectorScheduling } from '../../../../common/types/connectors'; import { CronEditor } from './cron_editor'; interface Props { disabled?: boolean; - onChange(scheduling: Connector['scheduling']): void; - scheduling: Connector['scheduling']; + onChange(scheduling: ConnectorScheduling): void; + scheduling: ConnectorScheduling; } export const EnterpriseSearchCronEditor: React.FC = ({ disabled, onChange, scheduling }) => { diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts index 731b9cadf6428..95902da5fdac7 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts @@ -145,6 +145,7 @@ describe('Setup Indices', () => { index_name: { type: 'keyword' }, is_native: { type: 'boolean' }, language: { type: 'keyword' }, + last_access_control_sync_error: { type: 'keyword' }, last_access_control_sync_scheduled_at: { type: 'date' }, last_access_control_sync_status: { type: 'keyword' }, last_deleted_document_count: { type: 'long' }, diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts index 5629cbbe073dd..f9c124bbb56dc 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts @@ -134,6 +134,7 @@ const connectorMappingsProperties: Record = { index_name: { type: 'keyword' }, is_native: { type: 'boolean' }, language: { type: 'keyword' }, + last_access_control_sync_error: { type: 'keyword' }, last_access_control_sync_scheduled_at: { type: 'date' }, last_access_control_sync_status: { type: 'keyword' }, last_deleted_document_count: { type: 'long' }, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts index f82ad3252cc76..fb373ed440dce 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts @@ -144,6 +144,7 @@ describe('addConnector lib function', () => { index_name: 'index_name', is_native: false, language: 'fr', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -159,7 +160,11 @@ describe('addConnector lib function', () => { reduce_whitespace: true, run_ml_inference: false, }, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: null, status: ConnectorStatus.CREATED, sync_now: false, @@ -330,6 +335,7 @@ describe('addConnector lib function', () => { index_name: 'index_name', is_native: true, language: null, + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -345,7 +351,11 @@ describe('addConnector lib function', () => { reduce_whitespace: true, run_ml_inference: false, }, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: null, status: ConnectorStatus.CREATED, sync_now: false, @@ -441,6 +451,7 @@ describe('addConnector lib function', () => { index_name: 'search-index_name', is_native: false, language: 'en', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -456,7 +467,11 @@ describe('addConnector lib function', () => { reduce_whitespace: true, run_ml_inference: false, }, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: null, status: ConnectorStatus.CREATED, sync_now: false, 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 493cb275da712..6c69273e2e240 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 @@ -40,6 +40,7 @@ describe('startSync lib function', () => { error: null, index_name: 'index_name', language: null, + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_seen: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts index 7ca6f779fb569..c314e101624fb 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts @@ -37,6 +37,7 @@ describe('addConnector lib function', () => { custom_scheduling: {}, error: null, index_name: 'index_name', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_seen: null, @@ -44,7 +45,11 @@ describe('addConnector lib function', () => { last_sync_scheduled_at: null, last_sync_status: null, last_synced: null, - scheduling: { enabled: false, interval: '* * * * *' }, + scheduling: { + access_control: { enabled: false, interval: '* * * * *' }, + full: { enabled: false, interval: '* * * * *' }, + incremental: { enabled: false, interval: '* * * * *' }, + }, service_type: null, status: 'not connected', sync_now: false, @@ -56,8 +61,12 @@ describe('addConnector lib function', () => { await expect( updateConnectorScheduling(mockClient as unknown as IScopedClusterClient, 'connectorId', { - enabled: true, - interval: '1 2 3 4 5', + access_control: { enabled: false, interval: '* * * * *' }, + full: { + enabled: true, + interval: '1 2 3 4 5', + }, + incremental: { enabled: false, interval: '* * * * *' }, }) ).resolves.toEqual({ _id: 'fakeId' }); expect(mockClient.asCurrentUser.index).toHaveBeenCalledWith({ @@ -68,6 +77,7 @@ describe('addConnector lib function', () => { custom_scheduling: {}, error: null, index_name: 'index_name', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_seen: null, @@ -75,7 +85,11 @@ describe('addConnector lib function', () => { last_sync_scheduled_at: null, last_sync_status: null, last_synced: null, - scheduling: { enabled: true, interval: '1 2 3 4 5' }, + scheduling: { + access_control: { enabled: false, interval: '* * * * *' }, + full: { enabled: true, interval: '1 2 3 4 5' }, + incremental: { enabled: false, interval: '* * * * *' }, + }, service_type: null, status: 'not connected', sync_now: false, @@ -94,8 +108,12 @@ describe('addConnector lib function', () => { }); await expect( updateConnectorScheduling(mockClient as unknown as IScopedClusterClient, 'connectorId', { - enabled: true, - interval: '1 2 3 4 5', + access_control: { enabled: false, interval: '* * * * *' }, + full: { + enabled: true, + interval: '1 2 3 4 5', + }, + incremental: { enabled: false, interval: '* * * * *' }, }) ).rejects.toEqual(new Error('Could not find document')); expect(mockClient.asCurrentUser.index).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.ts index 3672c3b05344a..b6da7f138421d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.ts @@ -10,12 +10,12 @@ import { i18n } from '@kbn/i18n'; import { CONNECTORS_INDEX } from '../..'; -import { ConnectorDocument, ConnectorScheduling } from '../../../common/types/connectors'; +import { ConnectorDocument, SchedulingConfiguraton } from '../../../common/types/connectors'; export const updateConnectorScheduling = async ( client: IScopedClusterClient, connectorId: string, - scheduling: ConnectorScheduling + scheduling: SchedulingConfiguraton ) => { const connectorResult = await client.asCurrentUser.get({ id: connectorId, diff --git a/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.test.ts b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.test.ts index eb3eda4d56455..8c6ad6cbb6a84 100644 --- a/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.test.ts @@ -90,6 +90,7 @@ describe('recreateConnectorDocument lib function', () => { index_name: 'indexName', is_native: false, language: '', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -100,7 +101,11 @@ describe('recreateConnectorDocument lib function', () => { last_synced: null, name: 'indexName', pipeline: null, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: 'elastic-crawler', status: ConnectorStatus.CONFIGURED, sync_now: false, diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 9cdb5cd06d9ec..40b9231b16ccd 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -125,7 +125,11 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { { path: '/internal/enterprise_search/connectors/{connectorId}/scheduling', validate: { - body: schema.object({ enabled: schema.boolean(), interval: schema.string() }), + body: schema.object({ + access_control: schema.object({ enabled: schema.boolean(), interval: schema.string() }), + full: schema.object({ enabled: schema.boolean(), interval: schema.string() }), + incremental: schema.object({ enabled: schema.boolean(), interval: schema.string() }), + }), params: schema.object({ connectorId: schema.string(), }), diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts index 5e77ddb87c078..458d223ce24b4 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts @@ -85,6 +85,7 @@ describe('createConnectorDocument', () => { index_name: 'indexName', is_native: false, language: 'fr', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -100,7 +101,11 @@ describe('createConnectorDocument', () => { reduce_whitespace: true, run_ml_inference: false, }, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: null, status: ConnectorStatus.CREATED, sync_now: false, @@ -181,6 +186,7 @@ describe('createConnectorDocument', () => { index_name: 'search-indexName', is_native: false, language: 'fr', + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -196,7 +202,11 @@ describe('createConnectorDocument', () => { reduce_whitespace: true, run_ml_inference: false, }, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: null, status: ConnectorStatus.CREATED, sync_now: false, diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts index f0dc759d19d42..965c41676a6ce 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts @@ -114,6 +114,7 @@ export function createConnectorDocument({ index_name: indexName, is_native: isNative, language, + last_access_control_sync_error: null, last_access_control_sync_scheduled_at: null, last_access_control_sync_status: null, last_incremental_sync_scheduled_at: null, @@ -124,7 +125,11 @@ export function createConnectorDocument({ last_synced: null, name: indexName.startsWith('search-') ? indexName.substring(7) : indexName, pipeline, - scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + }, service_type: serviceType || null, status: ConnectorStatus.CREATED, sync_now: false, diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts index c518dae975930..77f5c0b798b2f 100644 --- a/x-pack/plugins/fleet/common/constants/authz.ts +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -72,6 +72,18 @@ export const ENDPOINT_PRIVILEGES: Record = deepFreez privilegeType: 'api', privilegeName: 'readHostIsolationExceptions', }, + accessHostIsolationExceptions: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'accessHostIsolationExceptions', + }, + deleteHostIsolationExceptions: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'deleteHostIsolationExceptions', + }, writeBlocklist: { appId: DEFAULT_APP_CATEGORIES.security.id, privilegeSplit: '-', @@ -126,6 +138,12 @@ export const ENDPOINT_PRIVILEGES: Record = deepFreez privilegeType: 'api', privilegeName: 'writeHostIsolation', }, + writeHostIsolationRelease: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'writeHostIsolationRelease', + }, writeProcessOperations: { appId: DEFAULT_APP_CATEGORIES.security.id, privilegeSplit: '-', diff --git a/x-pack/plugins/fleet/common/constants/file_storage.ts b/x-pack/plugins/fleet/common/constants/file_storage.ts index 24994a28c9128..554176f40e263 100644 --- a/x-pack/plugins/fleet/common/constants/file_storage.ts +++ b/x-pack/plugins/fleet/common/constants/file_storage.ts @@ -5,16 +5,33 @@ * 2.0. */ -// File storage indexes supporting endpoint Upload/download +// 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-*'; +// File storage indexes supporting user uplaoded 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-*'; + // which integrations support file upload and the name to use for the file upload index -export const FILE_STORAGE_INTEGRATION_INDEX_NAMES: Readonly> = { - elastic_agent: 'agent', - endpoint: 'endpoint', +export const FILE_STORAGE_INTEGRATION_INDEX_NAMES: Readonly< + Record< + string, + { + /** 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 }, + endpoint: { name: 'endpoint', fromHost: true, toHost: true }, }; export const FILE_STORAGE_INTEGRATION_NAMES: Readonly = Object.keys( FILE_STORAGE_INTEGRATION_INDEX_NAMES diff --git a/x-pack/plugins/fleet/common/services/file_storage.test.ts b/x-pack/plugins/fleet/common/services/file_storage.test.ts new file mode 100644 index 0000000000000..0360d7311eb6a --- /dev/null +++ b/x-pack/plugins/fleet/common/services/file_storage.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getFileDataIndexName, getFileMetadataIndexName } from '..'; + +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'); + }); + + it('should generate file data index name for files received from host', () => { + expect(getFileDataIndexName('foo')).toEqual('.fleet-file-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'); + }); + + it('should generate file data index name for files to be delivered to host', () => { + expect(getFileDataIndexName('foo', true)).toEqual('.fleet-filedelivery-data-foo'); + }); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/file_storage.ts b/x-pack/plugins/fleet/common/services/file_storage.ts index b320a21ead584..6581f671df663 100644 --- a/x-pack/plugins/fleet/common/services/file_storage.ts +++ b/x-pack/plugins/fleet/common/services/file_storage.ts @@ -5,32 +5,54 @@ * 2.0. */ -import { FILE_STORAGE_DATA_INDEX_PATTERN, FILE_STORAGE_METADATA_INDEX_PATTERN } from '../constants'; +import { + FILE_STORAGE_DATA_INDEX_PATTERN, + FILE_STORAGE_METADATA_INDEX_PATTERN, + FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN, + FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN, +} from '../constants'; /** * Returns the index name for File Metadata storage for a given integration * @param integrationName + * @param forHostDelivery */ -export const getFileMetadataIndexName = (integrationName: string): string => { - if (FILE_STORAGE_METADATA_INDEX_PATTERN.indexOf('*') !== -1) { - return FILE_STORAGE_METADATA_INDEX_PATTERN.replace('*', integrationName); +export const getFileMetadataIndexName = ( + integrationName: string, + /** if set to true, then the index returned will be for files that are being sent to the host */ + forHostDelivery: boolean = false +): string => { + const metaIndex = forHostDelivery + ? FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN + : FILE_STORAGE_METADATA_INDEX_PATTERN; + + if (metaIndex.indexOf('*') !== -1) { + return metaIndex.replace('*', integrationName); } throw new Error( - `Unable to define integration file data index. No '*' in index pattern: ${FILE_STORAGE_METADATA_INDEX_PATTERN}` + `Unable to define integration file data index. No '*' in index pattern: ${metaIndex}` ); }; /** * Returns the index name for File data (chunks) storage for a given integration * @param integrationName */ -export const getFileDataIndexName = (integrationName: string): string => { - if (FILE_STORAGE_DATA_INDEX_PATTERN.indexOf('*') !== -1) { - return FILE_STORAGE_DATA_INDEX_PATTERN.replace('*', integrationName); +export const getFileDataIndexName = ( + integrationName: string, + /** if set to true, then the index returned will be for files that are being sent to the host */ + forHostDelivery: boolean = false +): string => { + const dataIndex = forHostDelivery + ? FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN + : FILE_STORAGE_DATA_INDEX_PATTERN; + + if (dataIndex.indexOf('*') !== -1) { + return dataIndex.replace('*', integrationName); } throw new Error( - `Unable to define integration file data index. No '*' in index pattern: ${FILE_STORAGE_DATA_INDEX_PATTERN}` + `Unable to define integration file data index. No '*' in index pattern: ${dataIndex}` ); }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 4bb8baae30241..cb0b966cc943b 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -62,14 +62,10 @@ import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, INTEGRATIONS_PLUGIN_ID, UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, - getFileMetadataIndexName, - getFileDataIndexName, } from '../common'; import { parseExperimentalConfigValue } from '../common/experimental_features'; -import { FleetFromHostFilesClient } from './services/files/client_from_host'; - -import type { FleetFromHostFileClientInterface } from './services/files/types'; +import { getFilesClientFactory } from './services/files/get_files_client_factory'; import type { MessageSigningServiceInterface } from './services/security'; import { @@ -130,8 +126,7 @@ import { UninstallTokenService, type UninstallTokenServiceInterface, } from './services/security/uninstall_token_service'; -import type { FleetToHostFileClientInterface } from './services/files/types'; -import { FleetToHostFilesClient } from './services/files/client_to_host'; +import type { FilesClientFactory } from './services/files/types'; export interface FleetSetupDeps { security: SecurityPluginSetup; @@ -231,28 +226,7 @@ export interface FleetStartContract { * @param type * @param maxSizeBytes */ - createFilesClient: Readonly<{ - /** - * Client to interact with files that will be sent to a host. - * @param packageName - * @param maxSizeBytes - */ - toHost: ( - /** The integration package name */ - packageName: string, - /** Max file size allow to be created (in bytes) */ - maxSizeBytes?: number - ) => FleetToHostFileClientInterface; - - /** - * Client to interact with files that were sent from the host - * @param packageName - */ - fromHost: ( - /** The integration package name */ - packageName: string - ) => FleetFromHostFileClientInterface; - }>; + createFilesClient: Readonly; messageSigningService: MessageSigningServiceInterface; uninstallTokenService: UninstallTokenServiceInterface; @@ -604,27 +578,12 @@ export class FleetPlugin createArtifactsClient(packageName: string) { return new FleetArtifactsClient(core.elasticsearch.client.asInternalUser, packageName); }, - createFilesClient: Object.freeze({ - fromHost: (packageName) => { - return new FleetFromHostFilesClient( - core.elasticsearch.client.asInternalUser, - this.initializerContext.logger.get('fleetFiles', packageName), - getFileMetadataIndexName(packageName), - getFileDataIndexName(packageName) - ); - }, - - toHost: (packageName, maxFileBytes) => { - return new FleetToHostFilesClient( - core.elasticsearch.client.asInternalUser, - this.initializerContext.logger.get('fleetFiles', packageName), - // FIXME:PT define once we have new index patterns (defend workflows team issue #6553) - getFileMetadataIndexName(packageName), - getFileDataIndexName(packageName), - maxFileBytes - ); - }, - }), + createFilesClient: Object.freeze( + getFilesClientFactory({ + esClient: core.elasticsearch.client.asInternalUser, + logger: this.initializerContext.logger, + }) + ), messageSigningService, uninstallTokenService, }; 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 8dd0c7b6c14c2..c99bbbd5a4f58 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 @@ -471,8 +471,28 @@ export async function ensureFileUploadWriteIndices(opts: { return Promise.all( integrationsWithFileUpload.flatMap((integrationName) => { - const indexName = FILE_STORAGE_INTEGRATION_INDEX_NAMES[integrationName]; - return [ensure(getFileDataIndexName(indexName)), ensure(getFileMetadataIndexName(indexName))]; + 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; }) ); } @@ -529,6 +549,8 @@ export async function ensureAliasHasWriteIndex(opts: { ); if (!existingIndex) { + logger.info(`Creating write index [${writeIndexName}], alias [${aliasName}]`); + await retryTransientEsErrors( () => esClient.indices.create({ index: writeIndexName, ...body }, { ignore: [404] }), { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts index f7e3161f8ad3c..a33dfdbe69629 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts @@ -207,6 +207,18 @@ describe('When using EPM `get` services', () => { version: '1.0.0', title: 'Nginx', } as any, + { + id: 'profiler_symbolizer', + name: 'profiler_symbolizer', + version: '1.0.0', + title: 'Profiler Symbolizer', + } as any, + { + id: 'profiler_collector', + name: 'profiler_collector', + version: '1.0.0', + title: 'Profiler Collector', + } as any, ]); MockRegistry.fetchFindLatestPackageOrUndefined.mockResolvedValue(undefined); MockRegistry.fetchInfo.mockResolvedValue({} as any); @@ -340,6 +352,48 @@ owner: elastic`, savedObjectType: PACKAGES_SAVED_OBJECT_TYPE, }); }); + + it('should hide profiling symbolizer', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockResolvedValue({ + saved_objects: [ + { + id: 'profiler_symbolizer', + attributes: { + name: 'profiler_symbolizer', + version: '0.0.1', + install_source: 'upload', + install_version: '0.0.1', + }, + }, + ], + } as any); + const packages = await getPackages({ + savedObjectsClient: soClient, + }); + expect(packages.find((item) => item.id === 'profiler_symbolizer')).toBeUndefined(); + }); + + it('should hide profiling collector', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockResolvedValue({ + saved_objects: [ + { + id: 'profiler_collector', + attributes: { + name: 'profiler_collector', + version: '0.0.1', + install_source: 'upload', + install_version: '0.0.1', + }, + }, + ], + } as any); + const packages = await getPackages({ + savedObjectsClient: soClient, + }); + expect(packages.find((item) => item.id === 'profiler_collector')).toBeUndefined(); + }); }); describe('getInstalledPackages', () => { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 586e53c7e6b0e..c06450e6e7657 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -122,6 +122,8 @@ export async function getPackages( ) ) .concat(uploadedPackagesNotInRegistry as Installable) + // hides profiling collector and symbolizer packages + .filter((item) => item.id !== 'profiler_collector' && item.id !== 'profiler_symbolizer') .sort(sortByName); for (const pkg of packageList) { 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 9d66a3cdcfe08..a4820f256da95 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 @@ -23,6 +23,11 @@ import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import type { File } from '@kbn/files-plugin/common'; +import { + FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN, + FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN, +} from '../../../common/constants'; + import { getFileDataIndexName, getFileMetadataIndexName } from '../../../common'; import type { HapiReadableStream } from '../..'; @@ -55,8 +60,8 @@ describe('FleetToHostFilesClient', () => { return new FleetToHostFilesClient( esClientMock, loggerMock, - getFileMetadataIndexName('foo'), - getFileDataIndexName('foo'), + getFileMetadataIndexName('foo', true), + getFileDataIndexName('foo', true), 12345 ); }; @@ -97,11 +102,19 @@ describe('FleetToHostFilesClient', () => { esClientMock.search.mockImplementation(async (searchRequest = {}) => { // File metadata - if ((searchRequest.index as string).startsWith('.fleet-files-')) { + if ( + (searchRequest.index as string).startsWith( + FILE_STORAGE_TO_HOST_METADATA_INDEX_PATTERN.replace('*', '') + ) + ) { return fleetFilesIndexSearchResponse; } - if ((searchRequest.index as string).startsWith('.fleet-file-data-')) { + if ( + (searchRequest.index as string).startsWith( + FILE_STORAGE_TO_HOST_DATA_INDEX_PATTERN.replace('*', '') + ) + ) { return fleetFileDataIndexSearchResponse; } @@ -117,9 +130,8 @@ describe('FleetToHostFilesClient', () => { expect(createEsFileClientMock).toHaveBeenCalledWith({ elasticsearchClient: esClientMock, logger: loggerMock, - // FIXME:PT adjust indexes once new index patterns are added to ES - metadataIndex: '.fleet-files-foo', - blobStorageIndex: '.fleet-file-data-foo', + metadataIndex: '.fleet-filedelivery-meta-foo', + blobStorageIndex: '.fleet-filedelivery-data-foo', maxSizeBytes: 12345, indexIsAlias: true, }); diff --git a/x-pack/plugins/fleet/server/services/files/get_files_client_factory.test.ts b/x-pack/plugins/fleet/server/services/files/get_files_client_factory.test.ts new file mode 100644 index 0000000000000..9921c911880f8 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/files/get_files_client_factory.test.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 { loggingSystemMock } from '@kbn/core-logging-server-mocks'; + +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; + +import type { FilesClientFactory } from './types'; +import { FleetFromHostFilesClient } from './client_from_host'; +import { FleetToHostFilesClient } from './client_to_host'; + +import { getFilesClientFactory } from './get_files_client_factory'; + +jest.mock('@kbn/files-plugin/server'); + +describe('getFilesClientFactory()', () => { + let clientFactory: FilesClientFactory; + + beforeEach(() => { + clientFactory = getFilesClientFactory({ + esClient: elasticsearchServiceMock.createElasticsearchClient(), + logger: loggingSystemMock.create().asLoggerFactory(), + }); + }); + + it('should return a client when `fromHost()` is called', () => { + expect(clientFactory.fromHost('endpoint')).toBeInstanceOf(FleetFromHostFilesClient); + }); + + it('should return a client when `toHost()` is called', () => { + expect(clientFactory.toHost('endpoint')).toBeInstanceOf(FleetToHostFilesClient); + }); + + it('should throw an error if `fromHost()` is called, but package name is not authorized', () => { + expect(() => clientFactory.fromHost('foo')).toThrow( + 'Integration name [foo] does not have access to files received from host' + ); + }); + + it('should throw an error if `toHost()` is called, but package name is not authorized', () => { + expect(() => clientFactory.toHost('foo')).toThrow( + 'Integration name [foo] does not have access to files for delivery to host' + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/files/get_files_client_factory.ts b/x-pack/plugins/fleet/server/services/files/get_files_client_factory.ts new file mode 100644 index 0000000000000..d79324faf28ad --- /dev/null +++ b/x-pack/plugins/fleet/server/services/files/get_files_client_factory.ts @@ -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 type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { LoggerFactory } from '@kbn/core/server'; + +import { FILE_STORAGE_INTEGRATION_INDEX_NAMES } from '../../../common/constants'; + +import { getFileDataIndexName, getFileMetadataIndexName } from '../../../common'; + +import { FleetFilesClientError } from '../../errors'; + +import { FleetToHostFilesClient } from './client_to_host'; + +import { FleetFromHostFilesClient } from './client_from_host'; + +import type { FilesClientFactory } from './types'; + +interface GetFilesClientFactoryParams { + esClient: ElasticsearchClient; + logger: LoggerFactory; +} + +export const getFilesClientFactory = ({ + esClient, + logger, +}: GetFilesClientFactoryParams): FilesClientFactory => { + return { + fromHost: (packageName) => { + if (!FILE_STORAGE_INTEGRATION_INDEX_NAMES[packageName]?.fromHost) { + throw new FleetFilesClientError( + `Integration name [${packageName}] does not have access to files received from host` + ); + } + + return new FleetFromHostFilesClient( + esClient, + logger.get('fleetFiles', packageName), + getFileMetadataIndexName(packageName), + getFileDataIndexName(packageName) + ); + }, + + toHost: (packageName, maxFileBytes) => { + if (!FILE_STORAGE_INTEGRATION_INDEX_NAMES[packageName]?.toHost) { + throw new FleetFilesClientError( + `Integration name [${packageName}] does not have access to files for delivery to host` + ); + } + + return new FleetToHostFilesClient( + esClient, + logger.get('fleetFiles', packageName), + getFileMetadataIndexName(packageName, true), + getFileDataIndexName(packageName, true), + maxFileBytes + ); + }, + }; +}; diff --git a/x-pack/plugins/fleet/server/services/files/types.ts b/x-pack/plugins/fleet/server/services/files/types.ts index 4a8759969ed53..18649b9f96e8f 100644 --- a/x-pack/plugins/fleet/server/services/files/types.ts +++ b/x-pack/plugins/fleet/server/services/files/types.ts @@ -119,3 +119,26 @@ export interface FileCustomMeta { target_agents: string[]; action_id: string; } + +export interface FilesClientFactory { + /** + * Client to interact with files that will be sent to a host. + * @param packageName + * @param maxSizeBytes + */ + toHost: ( + /** The integration package name */ + packageName: string, + /** Max file size allow to be created (in bytes) */ + maxSizeBytes?: number + ) => FleetToHostFileClientInterface; + + /** + * Client to interact with files that were sent from the host + * @param packageName + */ + fromHost: ( + /** The integration package name */ + packageName: string + ) => FleetFromHostFileClientInterface; +} diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts index 79bb4eaef32b7..8297f36769e1d 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts @@ -22,6 +22,8 @@ import { agentPolicyService } from '../../agent_policy'; import { UninstallTokenService, type UninstallTokenServiceInterface } from '.'; describe('UninstallTokenService', () => { + const aDayAgo = new Date(Date.now() - 24 * 3600 * 1000).toISOString(); + let soClientMock: jest.Mocked; let esoClientMock: jest.Mocked; let mockContext: MockedFleetAppContext; @@ -36,7 +38,6 @@ describe('UninstallTokenService', () => { policy_id: 'test-policy-id', token: 'test-token', }, - created_at: 'yesterday', } : { id: 'test-so-id', @@ -44,7 +45,6 @@ describe('UninstallTokenService', () => { policy_id: 'test-policy-id', token_plain: 'test-token-plain', }, - created_at: 'yesterday', }; } @@ -56,6 +56,7 @@ describe('UninstallTokenService', () => { policy_id: 'test-policy-id-two', token: 'test-token-two', }, + created_at: aDayAgo, } : { id: 'test-so-id-two', @@ -63,6 +64,7 @@ describe('UninstallTokenService', () => { policy_id: 'test-policy-id-two', token_plain: 'test-token-plain-two', }, + created_at: aDayAgo, }; } @@ -91,6 +93,9 @@ describe('UninstallTokenService', () => { { _id: defaultSO2.id, ...defaultSO2, + _source: { + created_at: defaultSO2.created_at, + }, }, ], }, @@ -186,7 +191,6 @@ describe('UninstallTokenService', () => { expect(token).toEqual({ policy_id: so.attributes.policy_id, token: getToken(so, canEncrypt), - created_at: 'yesterday', } as UninstallToken); }); @@ -202,11 +206,11 @@ describe('UninstallTokenService', () => { { policy_id: so.attributes.policy_id, token: getToken(so, canEncrypt), - created_at: 'yesterday', }, { policy_id: so2.attributes.policy_id, token: getToken(so2, canEncrypt), + created_at: aDayAgo, }, ] as UninstallToken[]); }); @@ -220,11 +224,11 @@ describe('UninstallTokenService', () => { { policy_id: so.attributes.policy_id, token: getToken(so, canEncrypt), - created_at: 'yesterday', }, { policy_id: so2.attributes.policy_id, token: getToken(so2, canEncrypt), + created_at: aDayAgo, }, ] as UninstallToken[]); }); diff --git a/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts b/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts index c1ae52b5a2167..d60453ca37d85 100644 --- a/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts +++ b/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts @@ -6,6 +6,7 @@ */ import type { GlobalSearchResult } from '@kbn/global-search-plugin/common/types'; +import { Tag } from '@kbn/saved-objects-tagging-plugin/public'; import { resultToOption } from './result_to_option'; const createSearchResult = (parts: Partial = {}): GlobalSearchResult => ({ @@ -89,4 +90,44 @@ describe('resultToOption', () => { }) ); }); + + it("doesn't crash on unknown tag", () => { + const input = createSearchResult({ + type: 'dashboard', + meta: { categoryLabel: 'category', displayName: 'foo', tagIds: ['known', 'unknown'] }, + }); + + const getTag = (tagId: string): Tag | undefined => { + if (tagId === 'known') { + return { + id: 'known', + name: 'Known', + description: 'Known', + color: '#000000', + }; + } + }; + + const logSpy = jest.spyOn(console, 'warn').mockImplementation(); + + const option = resultToOption(input, [], getTag); + expect(logSpy).toBeCalledWith( + 'SearchBar: Tag with id "unknown" not found. Tag "unknown" is referenced by the search result "dashboard:id". Skipping displaying the missing tag.' + ); + expect(option.append).toMatchInlineSnapshot(` + + `); + }); }); diff --git a/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx b/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx index 15f33dbf71e6c..871f2ace398a2 100644 --- a/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx +++ b/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx @@ -38,11 +38,22 @@ export const resultToOption = ( : [{ text: cleanMeta((meta.displayName as string) ?? type) }]; if (getTag && tagIds.length) { - // TODO #85189 - refactor to use TagList instead of getTag - // Casting to Tag[] because we know all our IDs will be valid here, no need to check for undefined - option.append = ( - - ); + const tags = tagIds.map(getTag).filter((tag, index) => { + if (!tag) { + // eslint-disable-next-line no-console + console.warn( + `SearchBar: Tag with id "${tagIds[index]}" not found. Tag "${tagIds[index]}" is referenced by the search result "${result.type}:${result.id}". Skipping displaying the missing tag.` + ); + return false; + } + + return true; + }) as Tag[]; + + if (tags.length) { + // TODO #85189 - refactor to use TagList instead of getTag + option.append = ; + } } return option; diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index 633933db750a8..44df9e5f7b544 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -542,6 +542,9 @@ describe('IndexPattern Data Source', () => { "type": "expression", }, ], + "ignoreGlobalFilters": Array [ + false, + ], "index": Array [ Object { "chain": Array [ @@ -1831,6 +1834,7 @@ describe('IndexPattern Data Source', () => { columns: {}, sampling: 1, linkToLayers: ['link-to-id'], + ignoreGlobalFilters: false, }, }, }); @@ -1921,6 +1925,7 @@ describe('IndexPattern Data Source', () => { indexPatternId: '1', columnOrder: [], columns: {}, + ignoreGlobalFilters: false, linkToLayers: ['some-layer'], sampling: 1, }, @@ -1951,7 +1956,12 @@ describe('IndexPattern Data Source', () => { newState: { ...state, layers: { - first: { ...state.layers.first, linkToLayers: undefined, sampling: 1 }, + first: { + ...state.layers.first, + linkToLayers: undefined, + sampling: 1, + ignoreGlobalFilters: false, + }, }, }, }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index 055d6a97f6183..59c8096c7d269 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -997,6 +997,7 @@ function blankLayer(indexPatternId: string, linkToLayers?: string[]): FormBasedL columns: {}, columnOrder: [], sampling: 1, + ignoreGlobalFilters: false, }; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx index eca0c032ee224..0a0c0dcc05eeb 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx @@ -1577,6 +1577,7 @@ describe('IndexPattern Data Source suggestions', () => { }, ], indexPatternId: '1', + ignoreGlobalFilters: false, }, ] as Layer[]; function stateWithoutLayer() { @@ -2084,6 +2085,27 @@ describe('IndexPattern Data Source suggestions', () => { }) ); }); + + it('should repserve the ignoreGlobalFilter flag if set', () => { + const updatedContext = [{ ...context[0], ignoreGlobalFilters: true }]; + const suggestions = getDatasourceSuggestionsForVisualizeCharts( + stateWithoutLayer(), + updatedContext, + expectedIndexPatterns + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + test: expect.objectContaining({ + ignoreGlobalFilters: true, + }), + }, + }), + }) + ); + }); }); describe('#getDatasourceSuggestionsForVisualizeField', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts index 81ce81bb49053..4bb6e5cdf8ec3 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts @@ -312,6 +312,7 @@ function createNewLayerWithMetricAggregationFromVizEditor( ) { const columns = convertToColumnChange(layer.columns, indexPattern); let newLayer: FormBasedLayer = { + ignoreGlobalFilters: layer.ignoreGlobalFilters, indexPatternId: indexPattern.id, columns: {}, columnOrder: [], diff --git a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx index aa5137a4aec09..d968b255b05a3 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx @@ -14,6 +14,7 @@ import { RandomSamplingSlider } from '@kbn/random-sampling'; import type { DatasourceLayerSettingsProps } from '../../types'; import type { FormBasedPrivateState } from './types'; import { isSamplingValueEnabled } from './utils'; +import { IgnoreGlobalFilterRowControl } from '../../shared_components/ignore_global_filter'; const samplingValues = [0.00001, 0.0001, 0.001, 0.01, 0.1, 1]; @@ -28,81 +29,95 @@ export function LayerSettingsPanel({ : state.layers[layerId].sampling; return ( - - -

    - - - - ), - }} - /> -

    - - } - label={ - <> - {i18n.translate('xpack.lens.indexPattern.randomSampling.label', { - defaultMessage: 'Sampling', - })}{' '} - - + + +

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

    + + } + label={ + <> + {i18n.translate('xpack.lens.indexPattern.randomSampling.label', { + defaultMessage: 'Sampling', + })}{' '} + - - - } - > - { - setState({ - ...state, - layers: { - ...state.layers, - [layerId]: { - ...state.layers[layerId], - sampling: newSamplingValue, + > + +
    + + } + > + { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + sampling: newSamplingValue, + }, }, - }, - }); + }); + }} + /> +
    + { + const newLayer = { + ...state.layers[layerId], + ignoreGlobalFilters: !state.layers[layerId].ignoreGlobalFilters, + }; + const newLayers = { ...state.layers }; + newLayers[layerId] = newLayer; + setState({ ...state, layers: newLayers }); }} /> - + ); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx index dce7474578e1a..3945067311e9b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx @@ -13,6 +13,7 @@ import type { DatasourceLayerPanelProps } from '../../types'; import type { FormBasedPrivateState } from './types'; import { ChangeIndexPattern } from '../../shared_components/dataview_picker/dataview_picker'; import { getSamplingValue } from './utils'; +import { getIgnoreGlobalFilterIcon } from '../../shared_components/ignore_global_filter'; export interface FormBasedLayerPanelProps extends DatasourceLayerPanelProps { state: FormBasedPrivateState; @@ -41,24 +42,28 @@ export function LayerPanel({ }); const samplingValue = getSamplingValue(layer); - const extraIconLabelProps = - samplingValue !== 1 - ? { - icon: { - component: ( - - ), - value: `${samplingValue * 100}%`, - tooltipValue: i18n.translate('xpack.lens.indexPattern.randomSamplingInfo', { - defaultMessage: '{value}% sampling', - values: { - value: samplingValue * 100, - }, - }), - 'data-test-subj': 'lnsChangeIndexPatternSamplingInfo', - }, - } - : {}; + const extraIcons = []; + if (layer.ignoreGlobalFilters) { + extraIcons.push( + getIgnoreGlobalFilterIcon({ + color: euiTheme.colors.disabledText, + dataTestSubj: 'lnsChangeIndexPatternIgnoringFilters', + }) + ); + } + if (samplingValue !== 1) { + extraIcons.push({ + component: , + value: `${samplingValue * 100}%`, + tooltipValue: i18n.translate('xpack.lens.indexPattern.randomSamplingInfo', { + defaultMessage: '{value}% sampling', + values: { + value: samplingValue * 100, + }, + }), + 'data-test-subj': 'lnsChangeIndexPatternSamplingInfo', + }); + } return ( ; sampling?: number; + ignoreGlobalFilters?: boolean; } export interface FormBasedPersistedState { diff --git a/x-pack/plugins/lens/public/datasources/form_based/utils.tsx b/x-pack/plugins/lens/public/datasources/form_based/utils.tsx index 4365eba33186e..8d6720c9f744a 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/utils.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/utils.tsx @@ -60,6 +60,7 @@ import { supportsRarityRanking } from './operations/definitions/terms'; import { DEFAULT_MAX_DOC_COUNT } from './operations/definitions/terms/constants'; import { getOriginalId } from '../../../common/expressions/datatable/transpose_helpers'; import { ReducedSamplingSectionEntries } from './info_badges'; +import { IgnoredGlobalFiltersEntries } from '../../shared_components/ignore_global_filter'; function isMinOrMaxColumn( column?: GenericIndexPatternColumn @@ -508,14 +509,13 @@ export function getNotifiableFeatures( if (!visualizationInfo) { return []; } - const layersWithCustomSamplingValues = Object.entries(state.layers).filter( + const features: UserMessage[] = []; + const layers = Object.entries(state.layers); + const layersWithCustomSamplingValues = layers.filter( ([, layer]) => getSamplingValue(layer) !== 1 ); - if (!layersWithCustomSamplingValues.length) { - return []; - } - return [ - { + if (layersWithCustomSamplingValues.length) { + features.push({ uniqueId: 'random_sampling_info', severity: 'info', fixableInEditor: false, @@ -530,8 +530,32 @@ export function getNotifiableFeatures( /> ), displayLocations: [{ id: 'embeddableBadge' }], - }, - ]; + }); + } + const layersWithIgnoreGlobalFilters = layers.filter(([, layer]) => layer.ignoreGlobalFilters); + if (layersWithIgnoreGlobalFilters.length) { + features.push({ + uniqueId: 'ignoring-global-filters-layers', + severity: 'info', + fixableInEditor: false, + shortMessage: i18n.translate('xpack.lens.xyChart.layerAnnotationsIgnoreTitle', { + defaultMessage: 'Layers ignoring global filters', + }), + longMessage: ( + ({ + layerId, + indexPatternId, + }))} + visualizationInfo={visualizationInfo} + dataViews={frame.dataViews} + /> + ), + displayLocations: [{ id: 'embeddableBadge' }], + }); + } + + return features; } /** diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx new file mode 100644 index 0000000000000..6e0e7077b2899 --- /dev/null +++ b/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx @@ -0,0 +1,234 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import userEvent from '@testing-library/user-event'; +import { EmbeddableFeatureBadge } from './embeddable_info_badges'; +import { UserMessage } from '../types'; + +describe('EmbeddableFeatureBadge', () => { + function renderPopup(messages: UserMessage[], count: number = messages.length) { + render(); + userEvent.click(screen.getByText(`${count}`)); + } + + it('should render no badge', () => { + render(); + expect(screen.queryByText('0')).not.toBeInTheDocument(); + }); + + it('should render the message in the popover', async () => { + render( + + ); + expect(screen.getByText('1')).toBeInTheDocument(); + userEvent.click(screen.getByText('1')); + expect(await screen.findByText('Long text')).toBeInTheDocument(); + }); + + it('should render a description of the badge in a tooltip on hover', async () => { + renderPopup([ + { + shortMessage: 'Short message', + longMessage: 'Long text', + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + ]); + expect(await screen.findByText('1 visualization modifier')).toBeInTheDocument(); + }); + + it('should render a separate section for each unique-id', async () => { + renderPopup([ + { + uniqueId: '1', + shortMessage: 'Section1', + longMessage: 'Long text', + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + { + uniqueId: '2', + shortMessage: 'Section2', + longMessage: 'Long text 2', + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + ]); + expect(await screen.findByText('Section1')).toBeInTheDocument(); + expect(await screen.findByText('Section2')).toBeInTheDocument(); + }); + + it('should group multiple messages with same id', async () => { + renderPopup( + [ + { + uniqueId: '1', + shortMessage: 'Section1', + longMessage:
    LongText1
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + { + uniqueId: '1', + shortMessage: 'Section1', + longMessage:
    LongText2
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + ], + 1 // messages are grouped + ); + expect(await screen.findByText('Section1')).toBeInTheDocument(); + expect(await screen.findAllByText('Section1')).toHaveLength(1); + expect(await screen.findAllByText('LongText', { exact: false })).toHaveLength(2); + }); + + it('should render messages without id first, then grouped messages', async () => { + renderPopup( + [ + { + shortMessage: 'Section2', + longMessage:
    AnotherText
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + { + uniqueId: '1', + shortMessage: 'Section1', + longMessage:
    LongText1
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + { + uniqueId: '1', + shortMessage: 'Section1', + longMessage:
    LongText2
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + ], + 2 // last two messages are grouped + ); + expect(await screen.findAllByText('Section', { exact: false })).toHaveLength(2); + // now check the order + const longMessages = await screen.findAllByText('Text', { exact: false }); + expect(longMessages[0]).toHaveTextContent('AnotherText'); + expect(longMessages[1]).toHaveTextContent('LongText1'); + }); + + describe('Horizontal rules', () => { + it('should render no rule for single message', async () => { + renderPopup([ + { + shortMessage: `Section1`, + longMessage:
    hello
    , + severity: 'info', + fixableInEditor: false, + displayLocations: [], + }, + ]); + expect( + await screen.queryByTestId('lns-feature-badges-horizontal-rule') + ).not.toBeInTheDocument(); + }); + it('should apply an horizontal if there are multiple messages without id', async () => { + const messages = [1, 2, 3].map((id) => ({ + shortMessage: `Section${id}`, + longMessage:
    {id}
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + })); + renderPopup(messages); + expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength( + messages.length - 1 + ); + }); + + it('should apply a rule between messages without id and grouped ones', async () => { + const messages = [ + { + uniqueId: 'myId', + shortMessage: `Section1`, + longMessage:
    Grouped
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + { + shortMessage: `Section2`, + longMessage:
    NoId
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + ]; + renderPopup(messages); + expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength(1); + }); + + it('should apply rules taking into account grouped messages', async () => { + const messages = [ + { + shortMessage: `Section2`, + longMessage:
    NoId
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + // #1 rule here + { + uniqueId: 'myId', + shortMessage: `Section1`, + longMessage:
    Grouped
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + { + uniqueId: 'myId', + shortMessage: `Section1`, + longMessage:
    Grouped 2
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + // #2 rule here + { + uniqueId: 'myOtherId', + shortMessage: `Section2`, + longMessage:
    Grouped3
    , + severity: 'info' as const, + fixableInEditor: false, + displayLocations: [], + }, + ]; + renderPopup(messages, 3); + expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength(2); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx index 88e91f34c4f35..fcfb33053ee68 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { Fragment } from 'react'; import { useState } from 'react'; import type { UserMessage } from '../types'; import './embeddable_info_badges.scss'; @@ -36,6 +36,20 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } count: messages.length, }, }); + + const messagesWithoutUniqueId = messages.filter(({ uniqueId }) => !uniqueId); + // compact messages be grouping longMessage together on matching unique-id + const messagesGroupedByUniqueId: Record = {}; + for (const message of messages) { + if (message.uniqueId) { + if (!messagesGroupedByUniqueId[message.uniqueId]) { + messagesGroupedByUniqueId[message.uniqueId] = []; + } + messagesGroupedByUniqueId[message.uniqueId].push(message); + } + } + const messageCount = + messagesWithoutUniqueId.length + Object.keys(messagesGroupedByUniqueId).length; return ( - {messages.length} + {messageCount} } @@ -72,13 +86,18 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } css={css` max-width: 280px; `} + data-test-subj="lns-feature-badges-panel" > - {messages.map(({ shortMessage, longMessage }, index) => { + {messagesWithoutUniqueId.map(({ shortMessage, longMessage }, index) => { return ( - <> - {index ? : null} + + {index ? ( + + ) : null} - + + ); + })} + {Object.entries(messagesGroupedByUniqueId).map(([uniqueId, messagesByUniqueId], index) => { + const hasHorizontalRule = messagesWithoutUniqueId.length || index; + const [{ shortMessage }] = messagesByUniqueId; + return ( + + {hasHorizontalRule ? ( + + ) : null} + + ); })} diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.test.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.test.tsx new file mode 100644 index 0000000000000..4b01fb823c7c1 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.test.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; +import { TriggerButton } from './trigger'; +import { EuiIcon } from '@elastic/eui'; + +describe('TriggerButton', () => { + describe('base version (no icons)', () => { + it('should render the basic button', () => { + render( + + ); + expect(screen.getByText('Trigger label')).toBeInTheDocument(); + }); + + it('should render the title if provided', () => { + render( + + ); + expect(screen.getByTitle('My title')).toBeInTheDocument(); + }); + + it('should call the toggle callback on click', () => { + const toggleFn = jest.fn(); + render( + + ); + userEvent.click(screen.getByText('Trigger')); + + expect(toggleFn).toHaveBeenCalled(); + }); + + it('should render the main label as red if missing', () => { + render( + + ); + // EUI danger red: rgb(171, 35, 28) + expect(screen.getByTestId('test-id')).toHaveStyle({ color: 'rgb(171, 35, 28)' }); + }); + }); + + describe('with icons', () => { + it('should render one icon', () => { + render( + , + tooltipValue: 'Ignore global filters', + 'data-test-subj': 'ignore-global-filters', + }, + ]} + /> + ); + + expect(screen.getByTestId('ignore-global-filters')).toBeInTheDocument(); + }); + + it('should render multiple icons', () => { + const indexes = [1, 2, 3]; + render( + ({ + component: , + tooltipValue: 'Ignore global filters', + 'data-test-subj': `ignore-global-filters-${index}`, + }))} + /> + ); + + for (const index of indexes) { + expect(screen.getByTestId(`ignore-global-filters-${index}`)).toBeInTheDocument(); + } + }); + + it('should render the value together with the provided component', () => { + render( + , + tooltipValue: 'Ignore global filters', + 'data-test-subj': 'ignore-global-filters', + value: 'Ignore filters', + }, + ]} + /> + ); + + expect(screen.getByText('Ignore filters')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx index 1efd517fc88d1..77bf60fc247c9 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx @@ -12,12 +12,12 @@ import { ToolbarButton, ToolbarButtonProps } from './toolbar_button'; interface TriggerLabelProps { label: string; - icon?: { + extraIcons?: Array<{ component: React.ReactElement; value?: string; tooltipValue?: string; 'data-test-subj': string; - }; + }>; } export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & @@ -27,10 +27,10 @@ export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & isDisabled?: boolean; }; -function TriggerLabel({ label, icon }: TriggerLabelProps) { +function TriggerLabel({ label, extraIcons }: TriggerLabelProps) { const { euiTheme } = useEuiTheme(); - if (!icon) { + if (!extraIcons?.length) { return <>{label}; } return ( @@ -44,28 +44,35 @@ function TriggerLabel({ label, icon }: TriggerLabelProps) { > {label}
    - - - - {icon.component} - {icon.value ? ( - - {icon.value} - - ) : null} - - - + {extraIcons.map((icon) => ( + + + + {icon.component} + {icon.value ? ( + + {icon.value} + + ) : null} + + + + ))}
    ); } @@ -75,7 +82,7 @@ export function TriggerButton({ title, togglePopover, isMissingCurrent, - icon, + extraIcons, ...rest }: ChangeIndexPatternTriggerProps & { togglePopover: () => void; @@ -96,7 +103,7 @@ export function TriggerButton({ {...rest} textProps={{ style: { width: '100%' } }} > - + ); } diff --git a/x-pack/plugins/lens/public/shared_components/ignore_global_filter/data_view_picker_icon.tsx b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/data_view_picker_icon.tsx new file mode 100644 index 0000000000000..f45db2e7bfa9a --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/data_view_picker_icon.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiIcon } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +/** + * Retrieve the IgnoreGlobalfilter shared icon to be put into the dataViewPicker within a Layer panel + * @param dataTestSubj test id to be applied + * @returns + */ +export const getIgnoreGlobalFilterIcon = ({ + color, + dataTestSubj, +}: { + color: string; + dataTestSubj: string; +}) => { + return { + component: ( + + ), + tooltipValue: i18n.translate('xpack.lens.layerPanel.ignoreGlobalFilters', { + defaultMessage: 'Ignore global filters', + }), + 'data-test-subj': dataTestSubj, + }; +}; diff --git a/x-pack/plugins/lens/public/shared_components/ignore_global_filter/index.ts b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/index.ts new file mode 100644 index 0000000000000..59ab4ad42631a --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getIgnoreGlobalFilterIcon } from './data_view_picker_icon'; +export { IgnoredGlobalFiltersEntries } from './info_badge'; +export { IgnoreGlobalFilterRowControl } from './settings_control'; diff --git a/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/info_badge.tsx similarity index 81% rename from x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx rename to x-pack/plugins/lens/public/shared_components/ignore_global_filter/info_badge.tsx index fcff325cb9b23..94e0b39ea37e3 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx +++ b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/info_badge.tsx @@ -7,16 +7,15 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { InfoBadge } from '../../shared_components/info_badges/info_badge'; +import { InfoBadge } from '../info_badges/info_badge'; import { FramePublicAPI, VisualizationInfo } from '../../types'; -import { XYAnnotationLayerConfig } from './types'; export function IgnoredGlobalFiltersEntries({ layers, visualizationInfo, dataViews, }: { - layers: XYAnnotationLayerConfig[]; + layers: Array<{ layerId: string; indexPatternId: string }>; visualizationInfo: VisualizationInfo; dataViews: FramePublicAPI['dataViews']; }) { @@ -27,8 +26,8 @@ export function IgnoredGlobalFiltersEntries({ const layerInfo = visualizationInfo.layers.find(({ layerId }) => layerId === layer.layerId); const layerTitle = layerInfo?.label || - i18n.translate('xpack.lens.xyChart.layerAnnotationsLabel', { - defaultMessage: 'Annotations', + i18n.translate('xpack.lens.layerTitle.fallbackLabel', { + defaultMessage: 'Layer', }); const layerPalette = layerInfo?.palette; return ( diff --git a/x-pack/plugins/lens/public/shared_components/ignore_global_filter/settings_control.tsx b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/settings_control.tsx new file mode 100644 index 0000000000000..df1af5f084c55 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/ignore_global_filter/settings_control.tsx @@ -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 { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export function IgnoreGlobalFilterRowControl({ + checked, + onChange, +}: { + checked: boolean; + onChange: (newValue: boolean) => void; +}) { + return ( + + onChange(!checked)} + compressed + /> + + ); +} diff --git a/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx b/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx index 55091aa0d1d40..503acd276fd1e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; +import { IgnoreGlobalFilterRowControl } from '../../shared_components/ignore_global_filter'; import type { VisualizationLayerSettingsProps } from '../../types'; import type { XYState } from './types'; import { isAnnotationsLayer } from './visualization_helpers'; @@ -26,28 +25,15 @@ export function LayerSettings({ return null; } return ( - - { - const layerIndex = state.layers.findIndex((l) => l === layer); - const newLayer = { ...layer, ignoreGlobalFilters: !layer.ignoreGlobalFilters }; - const newLayers = [...state.layers]; - newLayers[layerIndex] = newLayer; - setState({ ...state, layers: newLayers }); - }} - compressed - /> - + { + const layerIndex = state.layers.findIndex((l) => l === layer); + const newLayer = { ...layer, ignoreGlobalFilters: !layer.ignoreGlobalFilters }; + const newLayers = [...state.layers]; + newLayers[layerIndex] = newLayer; + setState({ ...state, layers: newLayers }); + }} + /> ); } diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index cc8897143a63d..d4f6d4411b5a7 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -110,8 +110,8 @@ import { defaultAnnotationLabel } from './annotations/helpers'; import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils'; import { createAnnotationActions } from './annotations/actions'; import { AddLayerButton } from './add_layer'; -import { IgnoredGlobalFiltersEntries } from './info_badges'; import { LayerSettings } from './layer_settings'; +import { IgnoredGlobalFiltersEntries } from '../../shared_components/ignore_global_filter'; const XY_ID = 'lnsXY'; @@ -1187,7 +1187,10 @@ function getNotifiableFeatures( }), longMessage: ( ({ + layerId, + indexPatternId, + }))} visualizationInfo={visualizationInfo} dataViews={frame.dataViews} /> 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 3f6c0a6f81754..eab7da089e07d 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 @@ -18,8 +18,8 @@ import { } from '@elastic/eui'; import { ToolbarButton } from '@kbn/kibana-react-plugin/public'; import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons'; -import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; +import { getIgnoreGlobalFilterIcon } from '../../../shared_components/ignore_global_filter/data_view_picker_icon'; import type { VisualizationLayerHeaderContentProps, VisualizationLayerWidgetProps, @@ -123,25 +123,6 @@ function AnnotationLayerHeaderContent({ const layer = state.layers[layerIndex] as XYAnnotationLayerConfig; const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId]; - const extraIconLabelProps = !layer.ignoreGlobalFilters - ? {} - : { - icon: { - component: ( - - ), - tooltipValue: i18n.translate('xpack.lens.layerPanel.ignoreGlobalFilters', { - defaultMessage: 'Ignore global filters', - }), - 'data-test-subj': 'lnsChangeIndexPatternIgnoringFilters', - }, - }; return ( ; static createDescriptor(descriptor: Partial) { if (typeof descriptor.jobId !== 'string') { @@ -197,13 +201,28 @@ export class AnomalySource implements IVectorSource { return false; } - async getImmutableProperties(): Promise { + async getImmutableProperties(dataFilters: DataFilters): Promise { + let explorerLink: string | undefined; + + try { + explorerLink = await AnomalySource.mlLocator?.getUrl({ + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: [this._descriptor.jobId], + timeRange: dataFilters.timeFilters, + }, + }); + } catch (error) { + // ignore error if unable to get link + } + return [ { label: i18n.translate('xpack.ml.maps.anomalySourcePropLabel', { defaultMessage: 'Job Id', }), value: this._descriptor.jobId, + ...(explorerLink ? { link: explorerLink } : {}), }, ]; } diff --git a/x-pack/plugins/ml/public/maps/anomaly_source_factory.ts b/x-pack/plugins/ml/public/maps/anomaly_source_factory.ts index 0556d703cdd96..6edd7839fbfae 100644 --- a/x-pack/plugins/ml/public/maps/anomaly_source_factory.ts +++ b/x-pack/plugins/ml/public/maps/anomaly_source_factory.ts @@ -7,35 +7,39 @@ import type { StartServicesAccessor } from '@kbn/core/public'; import { SOURCE_TYPES } from '@kbn/maps-plugin/common'; +import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { SerializableRecord } from '@kbn/utility-types'; import { HttpService } from '../application/services/http_service'; import type { MlPluginStart, MlStartDependencies } from '../plugin'; +import { ML_APP_LOCATOR } from '../../common/constants/locator'; import type { MlApiServices } from '../application/services/ml_api_service'; export class AnomalySourceFactory { public readonly type = SOURCE_TYPES.ES_ML_ANOMALIES; constructor( - private getStartServices: StartServicesAccessor, - private canGetJobs: boolean - ) { - this.canGetJobs = canGetJobs; - } + private getStartServices: StartServicesAccessor + ) {} - private async getServices(): Promise<{ mlResultsService: MlApiServices['results'] }> { - const [coreStart] = await this.getStartServices(); + private async getServices(): Promise<{ + mlResultsService: MlApiServices['results']; + mlLocator?: LocatorPublic; + }> { + const [coreStart, pluginStart] = await this.getStartServices(); const { mlApiServicesProvider } = await import('../application/services/ml_api_service'); + const mlLocator = pluginStart.share.url.locators.get(ML_APP_LOCATOR); const httpService = new HttpService(coreStart.http); const mlResultsService = mlApiServicesProvider(httpService).results; - return { mlResultsService }; + return { mlResultsService, mlLocator }; } public async create(): Promise { - const { mlResultsService } = await this.getServices(); + const { mlResultsService, mlLocator } = await this.getServices(); const { AnomalySource } = await import('./anomaly_source'); AnomalySource.mlResultsService = mlResultsService; - AnomalySource.canGetJobs = this.canGetJobs; + AnomalySource.mlLocator = mlLocator; return AnomalySource; } } diff --git a/x-pack/plugins/ml/public/maps/register_map_extension.ts b/x-pack/plugins/ml/public/maps/register_map_extension.ts index c4d32d5815903..1a223be9ead03 100644 --- a/x-pack/plugins/ml/public/maps/register_map_extension.ts +++ b/x-pack/plugins/ml/public/maps/register_map_extension.ts @@ -15,7 +15,7 @@ export async function registerMapExtension( core: MlCoreSetup, { canGetJobs, canCreateJobs }: { canGetJobs: boolean; canCreateJobs: boolean } ) { - const anomalySourceFactory = new AnomalySourceFactory(core.getStartServices, canGetJobs); + const anomalySourceFactory = new AnomalySourceFactory(core.getStartServices); const anomalyLayerWizardFactory = new AnomalyLayerWizardFactory( core.getStartServices, canGetJobs, diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index ddb8194c69115..8c18bdf495920 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -7,10 +7,11 @@ import { i18n } from '@kbn/i18n'; -export const SLO_BURN_RATE_RULE_ID = 'slo.rules.burnRate'; +export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate'; +export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.threshold'; + export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export const ALERT_STATUS_ALL = 'all'; - export const ALERTS_URL_STORAGE_KEY = '_a'; export const ALERT_ACTION_ID = 'slo.burnRate.alert'; diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 635208f40839d..3dffeca6db826 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -45,7 +45,6 @@ export { } from './progressive_loading'; export const sloFeatureId = 'slo'; - export const casesFeatureId = 'observabilityCases'; // The ID of the observability app. Should more appropriately be called diff --git a/x-pack/plugins/observability/common/threshold_rule/color_palette.ts b/x-pack/plugins/observability/common/threshold_rule/color_palette.ts new file mode 100644 index 0000000000000..d0b340765a788 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/color_palette.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 { difference, first, values } from 'lodash'; +import { euiPaletteColorBlind } from '@elastic/eui'; + +export enum Color { + color0 = 'color0', + color1 = 'color1', + color2 = 'color2', + color3 = 'color3', + color4 = 'color4', + color5 = 'color5', + color6 = 'color6', + color7 = 'color7', + color8 = 'color8', + color9 = 'color9', +} + +export type Palette = { + [K in keyof typeof Color]: string; +}; + +const euiPalette = euiPaletteColorBlind(); + +export const defaultPalette: Palette = { + [Color.color0]: euiPalette[1], // (blue) + [Color.color1]: euiPalette[2], // (pink) + [Color.color2]: euiPalette[0], // (green-ish) + [Color.color3]: euiPalette[3], // (purple) + [Color.color4]: euiPalette[4], // (light pink) + [Color.color5]: euiPalette[5], // (yellow) + [Color.color6]: euiPalette[6], // (tan) + [Color.color7]: euiPalette[7], // (orange) + [Color.color8]: euiPalette[8], // (brown) + [Color.color9]: euiPalette[9], // (red) +}; + +export const createPaletteTransformer = (palette: Palette) => (color: Color) => palette[color]; + +export const colorTransformer = createPaletteTransformer(defaultPalette); + +export const sampleColor = (usedColors: Color[] = []): Color => { + const available = difference(values(Color) as Color[], usedColors); + return first(available) || Color.color0; +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/constants.ts b/x-pack/plugins/observability/common/threshold_rule/constants.ts new file mode 100644 index 0000000000000..7eb3d0612373a --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/constants.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. + */ + +export const SNAPSHOT_CUSTOM_AGGREGATIONS = ['avg', 'max', 'min', 'rate'] as const; +export const TIMESTAMP_FIELD = '@timestamp'; +export const METRIC_EXPLORER_AGGREGATIONS = [ + 'avg', + 'max', + 'min', + 'cardinality', + 'rate', + 'count', + 'sum', + 'p95', + 'p99', + 'custom', +] as const; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts new file mode 100644 index 0000000000000..4299f37f40ab2 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InfraWaffleMapDataFormat } from './types'; +import { createBytesFormatter } from './bytes'; + +describe('createDataFormatter', () => { + it('should format bytes as bytesDecimal', () => { + const formatter = createBytesFormatter(InfraWaffleMapDataFormat.bytesDecimal); + expect(formatter(1000000)).toBe('1 MB'); + }); + it('should format bytes as bitsDecimal', () => { + const formatter = createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal); + expect(formatter(1000000)).toBe('8 Mbit'); + }); + it('should format bytes as abbreviatedNumber', () => { + const formatter = createBytesFormatter(InfraWaffleMapDataFormat.abbreviatedNumber); + expect(formatter(1000000)).toBe('1 M'); + }); +}); diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts new file mode 100644 index 0000000000000..50f3b6b0aa4b4 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.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 { formatNumber } from './number'; +import { InfraWaffleMapDataFormat } from './types'; + +/** + * The labels are derived from these two Wikipedia articles. + * https://en.wikipedia.org/wiki/Kilobit + * https://en.wikipedia.org/wiki/Kilobyte + */ +const LABELS = { + [InfraWaffleMapDataFormat.bytesDecimal]: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + [InfraWaffleMapDataFormat.bitsDecimal]: [ + 'bit', + 'kbit', + 'Mbit', + 'Gbit', + 'Tbit', + 'Pbit', + 'Ebit', + 'Zbit', + 'Ybit', + ], + [InfraWaffleMapDataFormat.abbreviatedNumber]: ['', 'K', 'M', 'B', 'T'], +}; + +const BASES = { + [InfraWaffleMapDataFormat.bytesDecimal]: 1000, + [InfraWaffleMapDataFormat.bitsDecimal]: 1000, + [InfraWaffleMapDataFormat.abbreviatedNumber]: 1000, +}; + +/* + * This formatter always assumes you're input is bytes and the output is a string + * in whatever format you've defined. Bytes in Format Out. + */ +export const createBytesFormatter = (format: InfraWaffleMapDataFormat) => (bytes: number) => { + const labels = LABELS[format]; + const base = BASES[format]; + const value = format === InfraWaffleMapDataFormat.bitsDecimal ? bytes * 8 : bytes; + // Use an exponential equation to get the power to determine which label to use. If the power + // is greater then the max label then use the max label. + const power = Math.min(Math.floor(Math.log(Math.abs(value)) / Math.log(base)), labels.length - 1); + if (power < 0) { + return `${formatNumber(value)} ${labels[0]}`; + } + return `${formatNumber(value / Math.pow(base, power))} ${labels[power]}`; +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts new file mode 100644 index 0000000000000..270dd7aa73808 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/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/observability/common/threshold_rule/formatters/high_precision.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.ts new file mode 100644 index 0000000000000..d111d61346736 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.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 formatHighPrecision = (val: number) => { + return Number(val).toLocaleString('en', { + maximumFractionDigits: 5, + }); +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/index.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/index.ts new file mode 100644 index 0000000000000..f7491ca139bfe --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/index.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createBytesFormatter } from './bytes'; +import { formatNumber } from './number'; +import { formatPercent } from './percent'; +import { ThresholdFormatterType } from '../types'; +import { formatHighPrecision } from './high_precision'; +import { InfraWaffleMapDataFormat } from './types'; + +export const FORMATTERS = { + number: formatNumber, + // Because the implimentation for formatting large numbers is the same as formatting + // bytes we are re-using the same code, we just format the number using the abbreviated number format. + abbreviatedNumber: createBytesFormatter(InfraWaffleMapDataFormat.abbreviatedNumber), + // bytes in bytes formatted string out + bytes: createBytesFormatter(InfraWaffleMapDataFormat.bytesDecimal), + // bytes in bits formatted string out + bits: createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal), + percent: formatPercent, + highPrecision: formatHighPrecision, +}; + +export const createFormatter = + (format: ThresholdFormatterType, template: string = '{{value}}') => + (val: string | number) => { + if (val == null) { + return ''; + } + const fmtFn = FORMATTERS[format]; + const value = fmtFn(Number(val)); + return template.replace(/{{value}}/g, value); + }; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/number.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/number.ts new file mode 100644 index 0000000000000..373d0d03af38b --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/number.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 formatNumber = (val: number) => { + return Number(val).toLocaleString('en', { + maximumFractionDigits: 1, + }); +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts new file mode 100644 index 0000000000000..eb907e6fd4775 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { formatNumber } from './number'; +export const formatPercent = (val: number) => { + return `${formatNumber(val * 100)}%`; +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts new file mode 100644 index 0000000000000..1715a28b1caab --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.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. + */ + +enum InfraFormatterType { + number = 'number', + abbreviatedNumber = 'abbreviatedNumber', + bytes = 'bytes', + bits = 'bits', + percent = 'percent', +} + +interface MetricFormatter { + formatter: InfraFormatterType; + template: string; + bounds?: { min: number; max: number }; +} + +interface MetricFormatters { + [key: string]: MetricFormatter; +} + +export const METRIC_FORMATTERS: MetricFormatters = { + ['count']: { formatter: InfraFormatterType.number, template: '{{value}}' }, + ['cpu']: { + formatter: InfraFormatterType.percent, + template: '{{value}}', + }, + ['memory']: { + formatter: InfraFormatterType.percent, + template: '{{value}}', + }, + ['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['logRate']: { + formatter: InfraFormatterType.abbreviatedNumber, + template: '{{value}}/s', + }, + ['diskIOReadBytes']: { + formatter: InfraFormatterType.bytes, + template: '{{value}}/s', + }, + ['diskIOWriteBytes']: { + formatter: InfraFormatterType.bytes, + template: '{{value}}/s', + }, + ['s3BucketSize']: { + formatter: InfraFormatterType.bytes, + template: '{{value}}', + }, + ['s3TotalRequests']: { + formatter: InfraFormatterType.abbreviatedNumber, + template: '{{value}}', + }, + ['s3NumberOfObjects']: { + formatter: InfraFormatterType.abbreviatedNumber, + template: '{{value}}', + }, + ['s3UploadBytes']: { + formatter: InfraFormatterType.bytes, + template: '{{value}}', + }, + ['s3DownloadBytes']: { + formatter: InfraFormatterType.bytes, + template: '{{value}}', + }, + ['sqsOldestMessage']: { + formatter: InfraFormatterType.number, + template: '{{value}} seconds', + }, + ['rdsLatency']: { + formatter: InfraFormatterType.number, + template: '{{value}} ms', + }, +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/types.ts b/x-pack/plugins/observability/common/threshold_rule/formatters/types.ts new file mode 100644 index 0000000000000..4bff7a122f4cd --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/formatters/types.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 enum InfraWaffleMapDataFormat { + bytesDecimal = 'bytesDecimal', + bitsDecimal = 'bitsDecimal', + abbreviatedNumber = 'abbreviatedNumber', +} diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts b/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts new file mode 100644 index 0000000000000..b6413b37b0380 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { metricValueFormatter } from './metric_value_formatter'; + +describe('metricValueFormatter', () => { + const testData = [ + { value: null, metric: undefined, result: '[NO DATA]' }, + { value: null, metric: 'system.cpu.user.pct', result: '[NO DATA]' }, + { value: 50, metric: undefined, result: '50' }, + { value: 0.7, metric: 'system.cpu.user.pct', result: '70%' }, + { value: 0.7012345, metric: 'system.cpu.user.pct', result: '70.1%' }, + { value: 208, metric: 'system.cpu.user.ticks', result: '208' }, + { value: 0.8, metric: 'system.cpu.user.ticks', result: '0.8' }, + ]; + + it.each(testData)( + 'metricValueFormatter($value, $metric) = $result', + ({ value, metric, result }) => { + expect(metricValueFormatter(value, metric)).toBe(result); + } + ); +}); diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts b/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts new file mode 100644 index 0000000000000..1ba0879fcf465 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { createFormatter } from './formatters'; + +export const metricValueFormatter = (value: number | null, metric: string = '') => { + const noDataValue = i18n.translate( + 'xpack.observability.threshold.rule.alerting.noDataFormattedValue', + { + defaultMessage: '[NO DATA]', + } + ); + + const formatter = metric.endsWith('.pct') + ? createFormatter('percent') + : createFormatter('highPrecision'); + + return value == null ? noDataValue : formatter(value); +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts new file mode 100644 index 0000000000000..d735e398e6661 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { xor } from 'lodash'; + +export const METRIC_EXPLORER_AGGREGATIONS = [ + 'avg', + 'max', + 'min', + 'cardinality', + 'rate', + 'count', + 'sum', + 'p95', + 'p99', + 'custom', +] as const; + +export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; + +type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; + +const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); + +export type MetricExplorerCustomMetricAggregations = Exclude< + MetricsExplorerAggregation, + 'custom' | 'rate' | 'p95' | 'p99' +>; +const metricsExplorerCustomMetricAggregationKeys = xor( + METRIC_EXPLORER_AGGREGATIONS, + OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS +).reduce>( + (acc, agg) => ({ ...acc, [agg]: null }), + {} as Record +); +export const metricsExplorerCustomMetricAggregationRT = rt.keyof( + metricsExplorerCustomMetricAggregationKeys +); + +export const metricsExplorerMetricRequiredFieldsRT = rt.type({ + aggregation: metricsExplorerAggregationRT, +}); + +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: metricsExplorerCustomMetricAggregationRT, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); + +export type MetricsExplorerCustomMetric = rt.TypeOf; + +export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ + field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, +}); + +export const metricsExplorerMetricRT = rt.intersection([ + metricsExplorerMetricRequiredFieldsRT, + metricsExplorerMetricOptionalFieldsRT, +]); + +export const timeRangeRT = rt.type({ + from: rt.number, + to: rt.number, + interval: rt.string, +}); + +export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ + timerange: timeRangeRT, + indexPattern: rt.string, + metrics: rt.array(metricsExplorerMetricRT), +}); + +const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); +export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); + +export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ + groupBy: rt.union([groupByRT, rt.array(groupByRT)]), + afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), + limit: rt.union([rt.number, rt.null, rt.undefined]), + filterQuery: rt.union([rt.string, rt.null, rt.undefined]), + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); + +export const metricsExplorerRequestBodyRT = rt.intersection([ + metricsExplorerRequestBodyRequiredFieldsRT, + metricsExplorerRequestBodyOptionalFieldsRT, +]); + +export const metricsExplorerPageInfoRT = rt.type({ + total: rt.number, + afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), +}); + +export const metricsExplorerColumnTypeRT = rt.keyof({ + date: null, + number: null, + string: null, +}); + +export const metricsExplorerColumnRT = rt.type({ + name: rt.string, + type: metricsExplorerColumnTypeRT, +}); + +export const metricsExplorerRowRT = rt.intersection([ + rt.type({ + timestamp: rt.number, + }), + rt.record( + rt.string, + rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) + ), +]); + +export const metricsExplorerSeriesRT = rt.intersection([ + rt.type({ + id: rt.string, + columns: rt.array(metricsExplorerColumnRT), + rows: rt.array(metricsExplorerRowRT), + }), + rt.partial({ + keys: rt.array(rt.string), + }), +]); + +export const metricsExplorerResponseRT = rt.type({ + series: rt.array(metricsExplorerSeriesRT), + pageInfo: metricsExplorerPageInfoRT, +}); + +export type AfterKey = rt.TypeOf; + +export type MetricsExplorerAggregation = rt.TypeOf; + +export type MetricsExplorerColumnType = rt.TypeOf; + +export type MetricsExplorerMetric = rt.TypeOf; + +export type MetricsExplorerPageInfo = rt.TypeOf; + +export type MetricsExplorerColumn = rt.TypeOf; + +export type MetricsExplorerRow = rt.TypeOf; + +export type MetricsExplorerSeries = rt.TypeOf; + +export type MetricsExplorerRequestBody = rt.TypeOf; + +export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/threshold_rule/types.ts b/x-pack/plugins/observability/common/threshold_rule/types.ts new file mode 100644 index 0000000000000..0f9dac9f88bd3 --- /dev/null +++ b/x-pack/plugins/observability/common/threshold_rule/types.ts @@ -0,0 +1,372 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { indexPatternRt } from '@kbn/io-ts-utils'; +import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; +import { values } from 'lodash'; +import { Color } from './color_palette'; +import { metricsExplorerMetricRT } from './metrics_explorer'; + +import { TimeUnitChar } from '../utils/formatters/duration'; +import { SNAPSHOT_CUSTOM_AGGREGATIONS } from './constants'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface DeepPartialArray extends Array> {} + +type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; +export type DeepPartial = T extends any[] + ? DeepPartialArray + : T extends object + ? DeepPartialObject + : T; + +export const ThresholdFormatterTypeRT = rt.keyof({ + abbreviatedNumber: null, + bits: null, + bytes: null, + number: null, + percent: null, + highPrecision: null, +}); +export type ThresholdFormatterType = rt.TypeOf; + +const pointRT = rt.type({ + timestamp: rt.number, + value: rt.number, +}); + +export type Point = rt.TypeOf; + +const serieRT = rt.type({ + id: rt.string, + points: rt.array(pointRT), +}); + +const seriesRT = rt.array(serieRT); + +export type Series = rt.TypeOf; + +export const getLogAlertsChartPreviewDataSuccessResponsePayloadRT = rt.type({ + data: rt.type({ + series: seriesRT, + }), +}); + +export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< + typeof getLogAlertsChartPreviewDataSuccessResponsePayloadRT +>; + +/** + * Properties specific to the Metrics Source Configuration. + */ +export const SourceConfigurationTimestampColumnRuntimeType = rt.type({ + timestampColumn: rt.type({ + id: rt.string, + }), +}); +export const SourceConfigurationMessageColumnRuntimeType = rt.type({ + messageColumn: rt.type({ + id: rt.string, + }), +}); + +export const SourceConfigurationFieldColumnRuntimeType = rt.type({ + fieldColumn: rt.type({ + id: rt.string, + field: rt.string, + }), +}); + +export const SourceConfigurationColumnRuntimeType = rt.union([ + SourceConfigurationTimestampColumnRuntimeType, + SourceConfigurationMessageColumnRuntimeType, + SourceConfigurationFieldColumnRuntimeType, +]); + +// Kibana data views +export const logDataViewReferenceRT = rt.type({ + type: rt.literal('data_view'), + dataViewId: rt.string, +}); + +export type LogDataViewReference = rt.TypeOf; + +// Index name +export const logIndexNameReferenceRT = rt.type({ + type: rt.literal('index_name'), + indexName: rt.string, +}); +export type LogIndexNameReference = rt.TypeOf; + +export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); + +/** + * Properties that represent a full source configuration, which is the result of merging static values with + * saved values. + */ +const SourceConfigurationFieldsRT = rt.type({ + message: rt.array(rt.string), +}); +export const SourceConfigurationRT = rt.type({ + name: rt.string, + description: rt.string, + metricAlias: rt.string, + logIndices: logIndexReferenceRT, + inventoryDefaultView: rt.string, + metricsExplorerDefaultView: rt.string, + fields: SourceConfigurationFieldsRT, + logColumns: rt.array(SourceConfigurationColumnRuntimeType), + anomalyThreshold: rt.number, +}); + +export const metricsSourceConfigurationPropertiesRT = rt.strict({ + name: SourceConfigurationRT.props.name, + description: SourceConfigurationRT.props.description, + metricAlias: SourceConfigurationRT.props.metricAlias, + inventoryDefaultView: SourceConfigurationRT.props.inventoryDefaultView, + metricsExplorerDefaultView: SourceConfigurationRT.props.metricsExplorerDefaultView, + anomalyThreshold: rt.number, +}); + +export type MetricsSourceConfigurationProperties = rt.TypeOf< + typeof metricsSourceConfigurationPropertiesRT +>; + +export const partialMetricsSourceConfigurationReqPayloadRT = rt.partial({ + ...metricsSourceConfigurationPropertiesRT.type.props, + metricAlias: indexPatternRt, +}); + +export const partialMetricsSourceConfigurationPropertiesRT = rt.partial({ + ...metricsSourceConfigurationPropertiesRT.type.props, +}); + +export type PartialMetricsSourceConfigurationProperties = rt.TypeOf< + typeof partialMetricsSourceConfigurationPropertiesRT +>; + +const metricsSourceConfigurationOriginRT = rt.keyof({ + fallback: null, + internal: null, + stored: null, +}); + +/** + * Source status + */ +const SourceStatusFieldRuntimeType = rt.type({ + name: rt.string, + type: rt.string, + searchable: rt.boolean, + aggregatable: rt.boolean, + displayable: rt.boolean, +}); +export const SourceStatusRuntimeType = rt.type({ + logIndicesExist: rt.boolean, + metricIndicesExist: rt.boolean, + remoteClustersExist: rt.boolean, + indexFields: rt.array(SourceStatusFieldRuntimeType), +}); +export const metricsSourceStatusRT = rt.strict({ + metricIndicesExist: SourceStatusRuntimeType.props.metricIndicesExist, + remoteClustersExist: SourceStatusRuntimeType.props.metricIndicesExist, + indexFields: SourceStatusRuntimeType.props.indexFields, +}); + +export type MetricsSourceStatus = rt.TypeOf; + +export const metricsSourceConfigurationRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + origin: metricsSourceConfigurationOriginRT, + configuration: metricsSourceConfigurationPropertiesRT, + }), + rt.partial({ + updatedAt: rt.number, + version: rt.string, + status: metricsSourceStatusRT, + }), + ]) +); + +export type MetricsSourceConfiguration = rt.TypeOf; +export type PartialMetricsSourceConfiguration = DeepPartial; + +export const metricsSourceConfigurationResponseRT = rt.type({ + source: metricsSourceConfigurationRT, +}); + +export type MetricsSourceConfigurationResponse = rt.TypeOf< + typeof metricsSourceConfigurationResponseRT +>; + +export enum Comparator { + GT = '>', + LT = '<', + GT_OR_EQ = '>=', + LT_OR_EQ = '<=', + BETWEEN = 'between', + OUTSIDE_RANGE = 'outside', +} + +export enum Aggregators { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', + P95 = 'p95', + P99 = 'p99', + CUSTOM = 'custom', +} + +const metricsExplorerOptionsMetricRT = rt.intersection([ + metricsExplorerMetricRT, + rt.partial({ + rate: rt.boolean, + color: rt.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), + label: rt.string, + }), +]); + +export type MetricsExplorerOptionsMetric = rt.TypeOf; + +export enum MetricsExplorerChartType { + line = 'line', + area = 'area', + bar = 'bar', +} + +export enum InfraRuleType { + MetricThreshold = 'metrics.alert.threshold', + InventoryThreshold = 'metrics.alert.inventory.threshold', + Anomaly = 'metrics.alert.anomaly', +} + +export enum AlertStates { + OK, + ALERT, + WARNING, + NO_DATA, + ERROR, +} + +const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); +const metricAnomalyMetricRT = rt.union([ + rt.literal('memory_usage'), + rt.literal('network_in'), + rt.literal('network_out'), +]); +const metricAnomalyInfluencerFilterRT = rt.type({ + fieldName: rt.string, + fieldValue: rt.string, +}); + +export interface MetricAnomalyParams { + nodeType: rt.TypeOf; + metric: rt.TypeOf; + alertInterval?: string; + sourceId?: string; + spaceId?: string; + threshold: Exclude; + influencerFilter: rt.TypeOf | undefined; +} + +// Types for the executor + +export interface MetricThresholdParams { + criteria: MetricExpressionParams[]; + filterQuery?: string; + filterQueryText?: string; + sourceId?: string; + alertOnNoData?: boolean; + alertOnGroupDisappear?: boolean; +} + +interface BaseMetricExpressionParams { + timeSize: number; + timeUnit: TimeUnitChar; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + warningComparator?: Comparator; + warningThreshold?: number[]; +} + +export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Exclude; + metric: string; +} + +export interface CountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.COUNT; +} + +export type CustomMetricAggTypes = Exclude< + Aggregators, + Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 +>; + +export interface MetricExpressionCustomMetric { + name: string; + aggType: CustomMetricAggTypes; + field?: string; + filter?: string; +} + +export interface CustomMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.CUSTOM; + customMetrics: MetricExpressionCustomMetric[]; + equation?: string; + label?: string; +} + +export type MetricExpressionParams = + | NonCountMetricExpressionParams + | CountMetricExpressionParams + | CustomMetricExpressionParams; + +export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID'); + +export type FilterQuery = string | typeof QUERY_INVALID; + +export interface AlertExecutionDetails { + alertId: string; + executionId: string; +} + +export enum InfraFormatterType { + number = 'number', + abbreviatedNumber = 'abbreviatedNumber', + bytes = 'bytes', + bits = 'bits', + percent = 'percent', +} + +export type SnapshotCustomAggregation = typeof SNAPSHOT_CUSTOM_AGGREGATIONS[number]; +const snapshotCustomAggregationKeys = SNAPSHOT_CUSTOM_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const SnapshotCustomAggregationRT = rt.keyof(snapshotCustomAggregationKeys); + +export const SnapshotCustomMetricInputRT = rt.intersection([ + rt.type({ + type: rt.literal('custom'), + field: rt.string, + aggregation: SnapshotCustomAggregationRT, + id: rt.string, + }), + rt.partial({ + label: rt.string, + }), +]); +export type SnapshotCustomMetricInput = rt.TypeOf; diff --git a/x-pack/plugins/observability/public/config/alert_feature_ids.ts b/x-pack/plugins/observability/public/config/alert_feature_ids.ts index 4e9c1ee26d07c..48dc3acf8311f 100644 --- a/x-pack/plugins/observability/public/config/alert_feature_ids.ts +++ b/x-pack/plugins/observability/public/config/alert_feature_ids.ts @@ -14,4 +14,5 @@ export const observabilityAlertFeatureIds: ValidFeatureId[] = [ AlertConsumers.LOGS, AlertConsumers.UPTIME, AlertConsumers.SLO, + AlertConsumers.OBSERVABILITY, ]; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 5be66178c7d2a..3b8e112187fa8 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -85,6 +85,7 @@ const withCore = makeDecorator({ metrics: { enabled: false }, uptime: { enabled: false }, }, + thresholdRule: { enabled: false }, }, coPilot: { enabled: false, diff --git a/x-pack/plugins/observability/public/pages/rules/rules.test.tsx b/x-pack/plugins/observability/public/pages/rules/rules.test.tsx index d4d3b8bd25881..7ddd1a833d405 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules.test.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules.test.tsx @@ -41,6 +41,7 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ metrics: { enabled: false }, uptime: { enabled: false }, }, + thresholdRule: { enabled: false }, }, coPilot: { enabled: false, 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 0306fb2c2875f..dc5b627716817 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 @@ -16,7 +16,7 @@ import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; import { isApmIndicatorType } from '../../../utils/slo/indicator'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; -import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; +import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../common/constants'; import { rulesLocatorID, sloFeatureId } from '../../../../common'; import { paths } from '../../../config/paths'; import { @@ -253,7 +253,7 @@ export function HeaderControl({ isLoading, slo }: Props) { {!!slo && isRuleFlyoutVisible ? ( 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 4564b133e4df9..f8bbdb9c3098d 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 @@ -33,7 +33,7 @@ import { transformSloResponseToCreateSloInput, transformValuesToCreateSLOInput, } from '../../slo_edit/helpers/process_slo_form_values'; -import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; +import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../common/constants'; import { rulesLocatorID, sloFeatureId } from '../../../../common'; import { paths } from '../../../config/paths'; import type { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; @@ -271,7 +271,7 @@ export function SloListItem({ { diff --git a/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap new file mode 100644 index 0000000000000..5ee10d2d3381e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AlertDetailsAppSection should render annotations 1`] = ` +Array [ + Object { + "annotations": Array [ + , + , + ], + "chartType": "line", + "derivedIndexPattern": Object { + "fields": Array [], + "title": "metricbeat-*", + }, + "expression": Object { + "aggType": "count", + "comparator": ">", + "threshold": Array [ + 2000, + ], + "timeSize": 15, + "timeUnit": "m", + }, + "filterQuery": undefined, + "groupBy": Array [ + "host.hostname", + ], + "hideTitle": true, + "source": Object { + "id": "default", + }, + "timeRange": Object { + "from": "2023-03-28T10:43:13.802Z", + "to": "2023-03-29T13:14:09.581Z", + }, + }, + Object {}, +] +`; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/expression_row.test.tsx.snap b/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/expression_row.test.tsx.snap new file mode 100644 index 0000000000000..4cf9b2195d554 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/__snapshots__/expression_row.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExpressionRow should render a helpText for the of expression 1`] = ` + + + , + } + } +/> +`; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.test.tsx new file mode 100644 index 0000000000000..d372bdbc95bee --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { + buildMetricThresholdAlert, + buildMetricThresholdRule, +} from '../mocks/metric_threshold_rule'; +import { AlertDetailsAppSection } from './alert_details_app_section'; +import { ExpressionChart } from './expression_chart'; + +const mockedChartStartContract = chartPluginMock.createStartContract(); + +jest.mock('@kbn/observability-alert-details', () => ({ + AlertAnnotation: () => {}, + AlertActiveTimeRangeAnnotation: () => {}, + getPaddedAlertTimeRange: () => ({ + from: '2023-03-28T10:43:13.802Z', + to: '2023-03-29T13:14:09.581Z', + }), +})); + +jest.mock('./expression_chart', () => ({ + ExpressionChart: jest.fn(() =>
    ), +})); + +jest.mock('../../../utils/kibana_react', () => ({ + useKibana: () => ({ + services: { + ...mockCoreMock.createStart(), + charts: mockedChartStartContract, + }, + }), +})); + +jest.mock('../helpers/source', () => ({ + withSourceProvider: () => jest.fn, + useSourceContext: () => ({ + source: { id: 'default' }, + createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }), + }), +})); + +describe('AlertDetailsAppSection', () => { + const queryClient = new QueryClient(); + const mockedSetAlertSummaryFields = jest.fn(); + const ruleLink = 'ruleLink'; + const renderComponent = () => { + return render( + + + + + + ); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render rule and alert data', async () => { + const result = renderComponent(); + + expect((await result.findByTestId('metricThresholdAppSection')).children.length).toBe(3); + expect(result.getByTestId('threshold-2000-2500')).toBeTruthy(); + }); + + it('should render rule link', async () => { + renderComponent(); + + expect(mockedSetAlertSummaryFields).toBeCalledTimes(1); + expect(mockedSetAlertSummaryFields).toBeCalledWith([ + { + label: 'Rule', + value: ( + + Monitoring hosts + + ), + }, + ]); + }); + + it('should render annotations', async () => { + const mockedExpressionChart = jest.fn(() =>
    ); + (ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart); + renderComponent(); + + expect(mockedExpressionChart).toHaveBeenCalledTimes(3); + expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.tsx new file mode 100644 index 0000000000000..b5ff1d62c2b54 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/alert_details_app_section.tsx @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo } from 'react'; +import moment from 'moment'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils'; +import { Rule } from '@kbn/alerting-plugin/common'; +import { + AlertAnnotation, + getPaddedAlertTimeRange, + AlertActiveTimeRangeAnnotation, +} from '@kbn/observability-alert-details'; +import { useKibana } from '../../../utils/kibana_react'; +import { metricValueFormatter } from '../../../../common/threshold_rule/metric_value_formatter'; +import { AlertSummaryField, TopAlert } from '../../..'; +import { generateUniqueKey } from '../lib/generate_unique_key'; + +import { ExpressionChart } from './expression_chart'; +import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; +import { Threshold } from './threshold'; +import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options'; +import { useSourceContext, withSourceProvider } from '../helpers/source'; +import { MetricThresholdRuleTypeParams } from '../types'; + +// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 +export type MetricThresholdRule = Rule< + MetricThresholdRuleTypeParams & { + filterQueryText?: string; + groupBy?: string | string[]; + } +>; +export type MetricThresholdAlert = TopAlert; + +const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm'; +const ALERT_START_ANNOTATION_ID = 'alert_start_annotation'; +const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation'; + +interface AppSectionProps { + alert: MetricThresholdAlert; + rule: MetricThresholdRule; + ruleLink: string; + setAlertSummaryFields: React.Dispatch>; +} + +export function AlertDetailsAppSection({ + alert, + rule, + ruleLink, + setAlertSummaryFields, +}: AppSectionProps) { + const { uiSettings, charts } = useKibana().services; + const { source, createDerivedIndexPattern } = useSourceContext(); + const { euiTheme } = useEuiTheme(); + + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); + const chartProps = { + theme: charts.theme.useChartsTheme(), + baseTheme: charts.theme.useChartsBaseTheme(), + }; + const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); + const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; + const annotations = [ + , + , + ]; + useEffect(() => { + setAlertSummaryFields([ + { + label: i18n.translate( + 'xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule', + { + defaultMessage: 'Rule', + } + ), + value: ( + + {rule.name} + + ), + }, + ]); + }, [alert, rule, ruleLink, setAlertSummaryFields]); + + return !!rule.params.criteria ? ( + + {rule.params.criteria.map((criterion, index) => ( + + + +

    + {criterion.aggType.toUpperCase()}{' '} + {'metric' in criterion ? criterion.metric : undefined} +

    +
    + + + + + + + + metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined) + } + title={i18n.translate( + 'xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle', + { + defaultMessage: 'Threshold breached', + } + )} + comparator={criterion.comparator} + /> + + + + + +
    +
    + ))} +
    + ) : null; +} +// eslint-disable-next-line import/no-default-export +export default withSourceProvider(AlertDetailsAppSection)('default'); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/pages/threshold/components/alert_flyout.tsx new file mode 100644 index 0000000000000..cc84ccd5081bd --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/alert_flyout.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useContext, useMemo } from 'react'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; +import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; + +import { TriggerActionsContext } from './triggers_actions_context'; +import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; +import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; + +interface Props { + visible?: boolean; + options?: Partial; + series?: MetricsExplorerSeries; + setVisible: React.Dispatch>; +} + +export function AlertFlyout(props: Props) { + const { visible, setVisible } = props; + const { triggersActionsUI } = useContext(TriggerActionsContext); + const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]); + const AddAlertFlyout = useMemo( + () => + triggersActionsUI && + triggersActionsUI.getAddRuleFlyout({ + consumer: 'infrastructure', + onClose: onCloseFlyout, + canChangeTrigger: false, + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + metadata: { + currentOptions: props.options, + series: props.series, + }, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [triggersActionsUI, onCloseFlyout] + ); + + return <>{visible && AddAlertFlyout}; +} + +export function PrefilledThresholdAlertFlyout({ onClose }: { onClose(): void }) { + const { metricThresholdPrefill } = useAlertPrefillContext(); + const { groupBy, filterQuery, metrics } = metricThresholdPrefill; + + return ; +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/autocomplete_field.tsx b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/autocomplete_field.tsx new file mode 100644 index 0000000000000..d47ce71e02b0e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/autocomplete_field.tsx @@ -0,0 +1,329 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFieldSearch, EuiOutsideClickDetector, EuiPanel } from '@elastic/eui'; +import React from 'react'; +import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; + +import { SuggestionItem } from './suggestion_item'; +export type StateUpdater = ( + prevState: Readonly, + prevProps: Readonly +) => State | null; + +function composeStateUpdaters(...updaters: Array>) { + return (state: State, props: Props) => + updaters.reduce((currentState, updater) => updater(currentState, props) || currentState, state); +} + +interface AutocompleteFieldProps { + isLoadingSuggestions: boolean; + isValid: boolean; + loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; + onSubmit?: (value: string) => void; + onChange?: (value: string) => void; + placeholder?: string; + suggestions: QuerySuggestion[]; + value: string; + disabled?: boolean; + autoFocus?: boolean; + 'aria-label'?: string; + compressed?: boolean; +} + +interface AutocompleteFieldState { + areSuggestionsVisible: boolean; + isFocused: boolean; + selectedIndex: number | null; +} + +export class AutocompleteField extends React.Component< + AutocompleteFieldProps, + AutocompleteFieldState +> { + public readonly state: AutocompleteFieldState = { + areSuggestionsVisible: false, + isFocused: false, + selectedIndex: null, + }; + + private inputElement: HTMLInputElement | null = null; + + public render() { + const { + suggestions, + isLoadingSuggestions, + isValid, + placeholder, + value, + disabled, + 'aria-label': ariaLabel, + compressed, + } = this.props; + const { areSuggestionsVisible, selectedIndex } = this.state; + + return ( + + + + {areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? ( + + {suggestions.map((suggestion, suggestionIndex) => ( + + ))} + + ) : null} + + + ); + } + + public componentDidMount() { + if (this.inputElement && this.props.autoFocus) { + this.inputElement.focus(); + } + } + + public componentDidUpdate(prevProps: AutocompleteFieldProps) { + const hasNewValue = prevProps.value !== this.props.value; + const hasNewSuggestions = prevProps.suggestions !== this.props.suggestions; + + if (hasNewValue) { + this.updateSuggestions(); + } + + if (hasNewValue && this.props.value === '') { + this.submit(); + } + + if (hasNewSuggestions && this.state.isFocused) { + this.showSuggestions(); + } + } + + private handleChangeInputRef = (element: HTMLInputElement | null) => { + this.inputElement = element; + }; + + private handleChange = (evt: React.ChangeEvent) => { + this.changeValue(evt.currentTarget.value); + }; + + private handleKeyDown = (evt: React.KeyboardEvent) => { + const { suggestions } = this.props; + + switch (evt.key) { + case 'ArrowUp': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState( + composeStateUpdaters(withSuggestionsVisible, withPreviousSuggestionSelected) + ); + } + break; + case 'ArrowDown': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState(composeStateUpdaters(withSuggestionsVisible, withNextSuggestionSelected)); + } else { + this.updateSuggestions(); + } + break; + case 'Enter': + evt.preventDefault(); + if (this.state.selectedIndex !== null) { + this.applySelectedSuggestion(); + } else { + this.submit(); + } + break; + case 'Escape': + evt.preventDefault(); + this.setState(withSuggestionsHidden); + break; + } + }; + + private handleKeyUp = (evt: React.KeyboardEvent) => { + switch (evt.key) { + case 'ArrowLeft': + case 'ArrowRight': + case 'Home': + case 'End': + this.updateSuggestions(); + break; + } + }; + + private handleFocus = () => { + this.setState(composeStateUpdaters(withSuggestionsVisible, withFocused)); + }; + + private handleBlur = () => { + this.setState(composeStateUpdaters(withSuggestionsHidden, withUnfocused)); + }; + + private selectSuggestionAt = (index: number) => () => { + this.setState(withSuggestionAtIndexSelected(index)); + }; + + private applySelectedSuggestion = () => { + if (this.state.selectedIndex !== null) { + this.applySuggestionAt(this.state.selectedIndex)(); + } + }; + + private applySuggestionAt = (index: number) => () => { + const { value, suggestions } = this.props; + const selectedSuggestion = suggestions[index]; + + if (!selectedSuggestion) { + return; + } + + const newValue = + value.substr(0, selectedSuggestion.start) + + selectedSuggestion.text + + value.substr(selectedSuggestion.end); + + this.setState(withSuggestionsHidden); + this.changeValue(newValue); + this.focusInputElement(); + }; + + private changeValue = (value: string) => { + const { onChange } = this.props; + + if (onChange) { + onChange(value); + } + }; + + private focusInputElement = () => { + if (this.inputElement) { + this.inputElement.focus(); + } + }; + + private showSuggestions = () => { + this.setState(withSuggestionsVisible); + }; + + private submit = () => { + const { isValid, onSubmit, value } = this.props; + + if (isValid && onSubmit) { + onSubmit(value); + } + + this.setState(withSuggestionsHidden); + }; + + private updateSuggestions = () => { + const inputCursorPosition = this.inputElement ? this.inputElement.selectionStart || 0 : 0; + this.props.loadSuggestions(this.props.value, inputCursorPosition, 200); + }; +} + +const withPreviousSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + props.suggestions.length - 1) % props.suggestions.length + : Math.max(props.suggestions.length - 1, 0), +}); + +const withNextSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + 1) % props.suggestions.length + : 0, +}); + +const withSuggestionAtIndexSelected = + (suggestionIndex: number) => + (state: AutocompleteFieldState, props: AutocompleteFieldProps): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length + ? suggestionIndex + : 0, + }); + +const withSuggestionsVisible = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: true, +}); + +const withSuggestionsHidden = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: false, + selectedIndex: null, +}); + +const withFocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: true, +}); + +const withUnfocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: false, +}); + +const AutocompleteContainer = euiStyled.div` + position: relative; +`; + +const SuggestionsPanel = euiStyled(EuiPanel).attrs(() => ({ + paddingSize: 'none', + hasShadow: true, +}))` + position: absolute; + width: 100%; + margin-top: 2px; + overflow-x: hidden; + overflow-y: scroll; + z-index: ${(props) => props.theme.eui.euiZLevel1}; + max-height: 322px; +`; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/index.ts b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/index.ts new file mode 100644 index 0000000000000..9fe8373d24a79 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/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 './autocomplete_field'; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/suggestion_item.tsx b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/suggestion_item.tsx new file mode 100644 index 0000000000000..67e4c879c7243 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/autocomplete_field/suggestion_item.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIcon } from '@elastic/eui'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { QuerySuggestion, QuerySuggestionTypes } from '@kbn/unified-search-plugin/public'; +import { transparentize } from 'polished'; + +interface Props { + isSelected?: boolean; + onClick?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + suggestion: QuerySuggestion; +} + +export function SuggestionItem(props: Props) { + const { isSelected, onClick, onMouseEnter, suggestion } = props; + + return ( + + + + + {suggestion.text} + {suggestion.description} + + ); +} + +SuggestionItem.defaultProps = { + isSelected: false, +}; + +const SuggestionItemContainer = euiStyled.div<{ + isSelected?: boolean; +}>` + display: flex; + flex-direction: row; + font-size: ${(props) => props.theme.eui.euiFontSizeS}; + height: ${(props) => props.theme.eui.euiSizeXL}; + white-space: nowrap; + background-color: ${(props) => + props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'}; +`; + +const SuggestionItemField = euiStyled.div` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: row; + height: ${(props) => props.theme.eui.euiSizeXL}; + padding: ${(props) => props.theme.eui.euiSizeXS}; +`; + +const SuggestionItemIconField = euiStyled(SuggestionItemField)<{ + suggestionType: QuerySuggestionTypes; +}>` + background-color: ${(props) => + transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))}; + color: ${(props) => getEuiIconColor(props.theme, props.suggestionType)}; + flex: 0 0 auto; + justify-content: center; + width: ${(props) => props.theme.eui.euiSizeXL}; +`; + +const SuggestionItemTextField = euiStyled(SuggestionItemField)` + flex: 2 0 0; + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; +`; + +const SuggestionItemDescriptionField = euiStyled(SuggestionItemField)` + flex: 3 0 0; + + p { + display: inline; + + span { + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; + } + } +`; + +const getEuiIconType = (suggestionType: QuerySuggestionTypes) => { + switch (suggestionType) { + case QuerySuggestionTypes.Field: + return 'kqlField'; + case QuerySuggestionTypes.Value: + return 'kqlValue'; + case QuerySuggestionTypes.RecentSearch: + return 'search'; + case QuerySuggestionTypes.Conjunction: + return 'kqlSelector'; + case QuerySuggestionTypes.Operator: + return 'kqlOperand'; + default: + return 'empty'; + } +}; + +const getEuiIconColor = (theme: any, suggestionType: QuerySuggestionTypes): string => { + switch (suggestionType) { + case QuerySuggestionTypes.Field: + return theme?.eui.euiColorVis7; + case QuerySuggestionTypes.Value: + return theme?.eui.euiColorVis0; + case QuerySuggestionTypes.Operator: + return theme?.eui.euiColorVis1; + case QuerySuggestionTypes.Conjunction: + return theme?.eui.euiColorVis2; + case QuerySuggestionTypes.RecentSearch: + default: + return theme?.eui.euiColorMediumShade; + } +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/criterion_preview_chart.tsx b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/criterion_preview_chart.tsx new file mode 100644 index 0000000000000..bde17caf668ce --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/criterion_preview_chart.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { niceTimeFormatter } from '@elastic/charts'; +import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import { EuiLoadingChart, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { sum, min as getMin, max as getMax } from 'lodash'; +import { GetLogAlertsChartPreviewDataSuccessResponsePayload } from '../../../../../common/threshold_rule/types'; +import { formatNumber } from '../../../../../common/threshold_rule/formatters/number'; + +type Series = GetLogAlertsChartPreviewDataSuccessResponsePayload['data']['series']; + +export const NUM_BUCKETS = 20; + +export const TIME_LABELS = { + s: i18n.translate('xpack.observability.threshold.rule..timeLabels.seconds', { + defaultMessage: 'seconds', + }), + m: i18n.translate('xpack.observability.threshold.rule..timeLabels.minutes', { + defaultMessage: 'minutes', + }), + h: i18n.translate('xpack.observability.threshold.rule..timeLabels.hours', { + defaultMessage: 'hours', + }), + d: i18n.translate('xpack.observability.threshold.rule..timeLabels.days', { + defaultMessage: 'days', + }), +}; + +export const useDateFormatter = (xMin?: number, xMax?: number) => { + const dateFormatter = useMemo(() => { + if (typeof xMin === 'number' && typeof xMax === 'number') { + return niceTimeFormatter([xMin, xMax]); + } else { + return (value: number) => `${value}`; + } + }, [xMin, xMax]); + return dateFormatter; +}; + +export const yAxisFormatter = formatNumber; + +export const getDomain = (series: Series, stacked: boolean = false) => { + let min: number | null = null; + let max: number | null = null; + const valuesByTimestamp = series.reduce<{ [timestamp: number]: number[] }>((acc, serie) => { + serie.points.forEach((point) => { + const valuesForTimestamp = acc[point.timestamp] || []; + acc[point.timestamp] = [...valuesForTimestamp, point.value]; + }); + return acc; + }, {}); + const pointValues = Object.values(valuesByTimestamp); + pointValues.forEach((results) => { + const maxResult = stacked ? sum(results) : getMax(results); + const minResult = getMin(results); + if (maxResult && (!max || maxResult > max)) { + max = maxResult; + } + if (minResult && (!min || minResult < min)) { + min = minResult; + } + }); + const timestampValues = Object.keys(valuesByTimestamp).map(Number); + const minTimestamp = getMin(timestampValues) || 0; + const maxTimestamp = getMax(timestampValues) || 0; + return { yMin: min || 0, yMax: max || 0, xMin: minTimestamp, xMax: maxTimestamp }; +}; + +// TODO use the EUI charts theme see src/plugins/charts/public/services/theme/README.md +export const getChartTheme = (isDarkMode: boolean): Theme => { + return isDarkMode ? DARK_THEME : LIGHT_THEME; +}; + +export const EmptyContainer: React.FC = ({ children }) => ( +
    + {children} +
    +); + +export const ChartContainer: React.FC = ({ children }) => ( +
    + {children} +
    +); + +export function NoDataState() { + return ( + + + + + + ); +} + +export function LoadingState() { + return ( + + + + + + ); +} + +export function ErrorState() { + return ( + + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx new file mode 100644 index 0000000000000..c07e053374644 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx @@ -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 { Color } from '../../../../../common/threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/threshold_rule/types'; +import { shallow } from 'enzyme'; +import React from 'react'; + +import { ThresholdAnnotations } from './threshold_annotations'; + +jest.mock('@elastic/charts', () => { + const original = jest.requireActual('@elastic/charts'); + + const mockComponent = (props: {}) => { + return
    ; + }; + + return { + ...original, + LineAnnotation: mockComponent, + RectAnnotation: mockComponent, + }; +}); + +describe('ThresholdAnnotations', () => { + async function setup(props = {}) { + const defaultProps = { + threshold: [20, 30], + sortedThresholds: [20, 30], + comparator: Comparator.GT, + color: Color.color0, + id: 'testId', + firstTimestamp: 123456789, + lastTimestamp: 987654321, + domain: { min: 10, max: 20 }, + }; + const wrapper = shallow(); + + return wrapper; + } + + it('should render a line annotation for each threshold', async () => { + const wrapper = await setup(); + + const annotation = wrapper.find('[data-test-subj="threshold-line"]'); + const expectedValues = [{ dataValue: 20 }, { dataValue: 30 }]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + expect(annotation.length).toBe(1); + }); + + it('should render a rectangular annotation for in between thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.BETWEEN }); + + const annotation = wrapper.find('[data-test-subj="between-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 20, + y1: 30, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render an upper rectangular annotation for outside range thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.OUTSIDE_RANGE }); + + const annotation = wrapper.find('[data-test-subj="outside-range-lower-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 10, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a lower rectangular annotation for outside range thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.OUTSIDE_RANGE }); + + const annotation = wrapper.find('[data-test-subj="outside-range-upper-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 30, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a rectangular annotation for below thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.LT }); + + const annotation = wrapper.find('[data-test-subj="below-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 10, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a rectangular annotation for above thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.GT }); + + const annotation = wrapper.find('[data-test-subj="above-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 20, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.tsx b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.tsx new file mode 100644 index 0000000000000..09bd7e21fdc10 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/criterion_preview_chart/threshold_annotations.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { AnnotationDomainType, LineAnnotation, RectAnnotation } from '@elastic/charts'; +import { first, last } from 'lodash'; +import React from 'react'; +import { Color, colorTransformer } from '../../../../../common/threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/threshold_rule/types'; + +interface ThresholdAnnotationsProps { + threshold: number[]; + sortedThresholds: number[]; + comparator: Comparator; + color: Color; + id: string; + firstTimestamp: number; + lastTimestamp: number; + domain: { min: number; max: number }; +} + +const opacity = 0.3; + +export function ThresholdAnnotations({ + threshold, + sortedThresholds, + comparator, + color, + id, + firstTimestamp, + lastTimestamp, + domain, +}: ThresholdAnnotationsProps) { + if (!comparator || !threshold) return null; + const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(comparator); + const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(comparator); + return ( + <> + ({ + dataValue: t, + }))} + style={{ + line: { + strokeWidth: 2, + stroke: colorTransformer(color), + opacity: 1, + }, + }} + /> + {sortedThresholds.length === 2 && comparator === Comparator.BETWEEN ? ( + <> + + + ) : null} + {sortedThresholds.length === 2 && comparator === Comparator.OUTSIDE_RANGE ? ( + <> + + + + ) : null} + {isBelow && first(threshold) != null ? ( + + ) : null} + {isAbove && first(threshold) != null ? ( + + ) : null} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.stories.tsx new file mode 100644 index 0000000000000..25cff41cd8ac7 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react/types-6-0'; +import React, { useCallback, useEffect, useState } from 'react'; +import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_utils/use_global_storybook_theme'; +import { + Aggregators, + Comparator, + MetricExpressionParams, +} from '../../../../../common/threshold_rule/types'; +import { TimeUnitChar } from '../../../../../common'; + +import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; +import { aggregationType } from '../expression_row'; +import { MetricExpression } from '../../types'; +import { validateMetricThreshold } from '../validation'; + +export default { + title: 'infra/alerting/CustomEquationEditor', + decorators: [ + (wrappedStory) =>
    {wrappedStory()}
    , + decorateWithGlobalStorybookThemeProviders, + ], + parameters: { + layout: 'padded', + }, + argTypes: { + onChange: { action: 'changed' }, + }, +} as Meta; + +const fakeDataView = { + title: 'metricbeat-*', + fields: [ + { + name: 'system.cpu.user.pct', + type: 'number', + }, + { + name: 'system.cpu.system.pct', + type: 'number', + }, + { + name: 'system.cpu.cores', + type: 'number', + }, + ], +}; + +const CustomEquationEditorTemplate: Story = (args) => { + const [expression, setExpression] = useState(args.expression); + const [errors, setErrors] = useState(args.errors); + + const handleExpressionChange = useCallback( + (exp: MetricExpression) => { + setExpression(exp); + args.onChange(exp); + return exp; + }, + [args] + ); + + useEffect(() => { + const validationObject = validateMetricThreshold({ + criteria: [expression as MetricExpressionParams], + }); + setErrors(validationObject.errors[0]); + }, [expression]); + + return ( + + ); +}; + +export const CustomEquationEditorDefault = CustomEquationEditorTemplate.bind({}); +export const CustomEquationEditorWithEquationErrors = CustomEquationEditorTemplate.bind({}); +export const CustomEquationEditorWithFieldError = CustomEquationEditorTemplate.bind({}); + +const BASE_ARGS = { + expression: { + aggType: Aggregators.CUSTOM, + timeSize: 1, + timeUnit: 'm' as TimeUnitChar, + threshold: [1], + comparator: Comparator.GT, + }, + fields: [ + { name: 'system.cpu.user.pct', normalizedType: 'number' }, + { name: 'system.cpu.system.pct', normalizedType: 'number' }, + { name: 'system.cpu.cores', normalizedType: 'number' }, + ], + aggregationTypes: aggregationType, +}; + +CustomEquationEditorDefault.args = { + ...BASE_ARGS, + errors: {}, +}; + +CustomEquationEditorWithEquationErrors.args = { + ...BASE_ARGS, + expression: { + ...BASE_ARGS.expression, + equation: 'Math.round(A / B)', + customMetrics: [ + { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.user.pct' }, + { name: 'B', aggType: Aggregators.MAX, field: 'system.cpu.cores' }, + ], + }, + errors: { + equation: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + }, +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.tsx new file mode 100644 index 0000000000000..68568e51dd29c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/custom_equation_editor.tsx @@ -0,0 +1,222 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiFieldText, + EuiFormRow, + EuiFlexItem, + EuiFlexGroup, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState, useCallback, useMemo } from 'react'; +import { omit, range, first, xor, debounce } from 'lodash'; +import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DataViewBase } from '@kbn/es-query'; +import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/threshold_rule/metrics_explorer'; +import { + Aggregators, + CustomMetricAggTypes, + MetricExpressionCustomMetric, +} from '../../../../../common/threshold_rule/types'; + +import { MetricExpression } from '../../types'; +import { CustomMetrics, AggregationTypes, NormalizedFields } from './types'; +import { MetricRowWithAgg } from './metric_row_with_agg'; +import { MetricRowWithCount } from './metric_row_with_count'; +import { + CUSTOM_EQUATION, + EQUATION_HELP_MESSAGE, + LABEL_HELP_MESSAGE, + LABEL_LABEL, +} from '../../i18n_strings'; + +export interface CustomEquationEditorProps { + onChange: (expression: MetricExpression) => void; + expression: MetricExpression; + fields: NormalizedFields; + aggregationTypes: AggregationTypes; + errors: IErrorObject; + dataView: DataViewBase; +} + +const NEW_METRIC = { name: 'A', aggType: Aggregators.AVERAGE as CustomMetricAggTypes }; +const MAX_VARIABLES = 26; +const CHAR_CODE_FOR_A = 65; +const CHAR_CODE_FOR_Z = CHAR_CODE_FOR_A + MAX_VARIABLES; +const VAR_NAMES = range(CHAR_CODE_FOR_A, CHAR_CODE_FOR_Z).map((c) => String.fromCharCode(c)); + +export function CustomEquationEditor({ + onChange, + expression, + fields, + aggregationTypes, + errors, + dataView, +}: CustomEquationEditorProps) { + const [customMetrics, setCustomMetrics] = useState( + expression?.customMetrics ?? [NEW_METRIC] + ); + const [label, setLabel] = useState(expression?.label || undefined); + const [equation, setEquation] = useState(expression?.equation || undefined); + const debouncedOnChange = useMemo(() => debounce(onChange, 500), [onChange]); + + const handleAddNewRow = useCallback(() => { + setCustomMetrics((previous) => { + const currentVars = previous?.map((m) => m.name) ?? []; + const name = first(xor(VAR_NAMES, currentVars))!; + const nextMetrics = [...(previous || []), { ...NEW_METRIC, name }]; + debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label }); + return nextMetrics; + }); + }, [debouncedOnChange, equation, expression, label]); + + const handleDelete = useCallback( + (name: string) => { + setCustomMetrics((previous) => { + const nextMetrics = previous?.filter((row) => row.name !== name) ?? [NEW_METRIC]; + const finalMetrics = (nextMetrics.length && nextMetrics) || [NEW_METRIC]; + debouncedOnChange({ ...expression, customMetrics: finalMetrics, equation, label }); + return finalMetrics; + }); + }, + [equation, expression, debouncedOnChange, label] + ); + + const handleChange = useCallback( + (metric: MetricExpressionCustomMetric) => { + setCustomMetrics((previous) => { + const nextMetrics = previous?.map((m) => (m.name === metric.name ? metric : m)); + debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label }); + return nextMetrics; + }); + }, + [equation, expression, debouncedOnChange, label] + ); + + const handleEquationChange = useCallback( + (e: React.ChangeEvent) => { + setEquation(e.target.value); + debouncedOnChange({ ...expression, customMetrics, equation: e.target.value, label }); + }, + [debouncedOnChange, expression, customMetrics, label] + ); + + const handleLabelChange = useCallback( + (e: React.ChangeEvent) => { + setLabel(e.target.value); + debouncedOnChange({ ...expression, customMetrics, equation, label: e.target.value }); + }, + [debouncedOnChange, expression, customMetrics, equation] + ); + + const disableAdd = customMetrics?.length === MAX_VARIABLES; + const disableDelete = customMetrics?.length === 1; + + const filteredAggregationTypes = omit(aggregationTypes, OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS); + + const metricRows = customMetrics?.map((row) => { + if (row.aggType === Aggregators.COUNT) { + return ( + + ); + } + return ( + + ); + }); + + const placeholder = useMemo(() => { + return customMetrics?.map((row) => row.name).join(' + '); + }, [customMetrics]); + + return ( +
    + + {metricRows} + + + + + + + + + + + + + + + + + + + + + +
    + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/index.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/index.tsx new file mode 100644 index 0000000000000..2c885581b3989 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { CustomEquationEditor } from './custom_equation_editor'; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_controls.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_controls.tsx new file mode 100644 index 0000000000000..9cc103c591118 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_controls.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; +import { DELETE_LABEL } from '../../i18n_strings'; + +interface MetricRowControlProps { + onDelete: () => void; + disableDelete: boolean; +} + +export function MetricRowControls({ onDelete, disableDelete }: MetricRowControlProps) { + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_agg.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_agg.tsx new file mode 100644 index 0000000000000..10ff425e06525 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_agg.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFormRow, + EuiHorizontalRule, + EuiFlexItem, + EuiFlexGroup, + EuiSelect, + EuiComboBox, + EuiComboBoxOptionOption, +} from '@elastic/eui'; +import React, { useMemo, useCallback } from 'react'; +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { ValidNormalizedTypes } from '@kbn/triggers-actions-ui-plugin/public'; +import { Aggregators, CustomMetricAggTypes } from '../../../../../common/threshold_rule/types'; +import { MetricRowControls } from './metric_row_controls'; +import { NormalizedFields, MetricRowBaseProps } from './types'; + +interface MetricRowWithAggProps extends MetricRowBaseProps { + aggType?: CustomMetricAggTypes; + field?: string; + fields: NormalizedFields; +} + +export function MetricRowWithAgg({ + name, + aggType = Aggregators.AVERAGE, + field, + onDelete, + disableDelete, + fields, + aggregationTypes, + onChange, + errors, +}: MetricRowWithAggProps) { + const handleDelete = useCallback(() => { + onDelete(name); + }, [name, onDelete]); + + const fieldOptions = useMemo( + () => + fields.reduce((acc, fieldValue) => { + if ( + aggType && + aggregationTypes[aggType].validNormalizedTypes.includes( + fieldValue.normalizedType as ValidNormalizedTypes + ) + ) { + acc.push({ label: fieldValue.name }); + } + return acc; + }, [] as Array<{ label: string }>), + [fields, aggregationTypes, aggType] + ); + + const aggOptions = useMemo( + () => + Object.values(aggregationTypes).map((a) => ({ + text: a.text, + value: a.value, + })), + [aggregationTypes] + ); + + const handleFieldChange = useCallback( + (selectedOptions: EuiComboBoxOptionOption[]) => { + onChange({ + name, + field: (selectedOptions.length && selectedOptions[0].label) || undefined, + aggType, + }); + }, + [name, aggType, onChange] + ); + + const handleAggChange = useCallback( + (el: React.ChangeEvent) => { + onChange({ + name, + field, + aggType: el.target.value as CustomMetricAggTypes, + }); + }, + [name, field, onChange] + ); + + const isAggInvalid = get(errors, ['customMetrics', name, 'aggType']) != null; + const isFieldInvalid = get(errors, ['customMetrics', name, 'field']) != null || !field; + + return ( + <> + + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_count.tsx b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_count.tsx new file mode 100644 index 0000000000000..e27efafde504c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/metric_row_with_count.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFormRow, EuiHorizontalRule, EuiFlexItem, EuiFlexGroup, EuiSelect } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { DataViewBase } from '@kbn/es-query'; +import { Aggregators, CustomMetricAggTypes } from '../../../../../common/threshold_rule/types'; +import { MetricRowControls } from './metric_row_controls'; +import { MetricRowBaseProps } from './types'; +import { MetricsExplorerKueryBar } from '../kuery_bar'; + +interface MetricRowWithCountProps extends MetricRowBaseProps { + agg?: Aggregators; + filter?: string; + dataView: DataViewBase; +} + +export function MetricRowWithCount({ + name, + agg, + filter, + onDelete, + disableDelete, + onChange, + aggregationTypes, + dataView, +}: MetricRowWithCountProps) { + const aggOptions = useMemo( + () => + Object.values(aggregationTypes) + .filter((aggType) => aggType.value !== Aggregators.CUSTOM) + .map((aggType) => ({ + text: aggType.text, + value: aggType.value, + })), + [aggregationTypes] + ); + + const handleDelete = useCallback(() => { + onDelete(name); + }, [name, onDelete]); + + const handleAggChange = useCallback( + (el: React.ChangeEvent) => { + onChange({ + name, + filter, + aggType: el.target.value as CustomMetricAggTypes, + }); + }, + [name, filter, onChange] + ); + + const handleFilterChange = useCallback( + (filterString: string) => { + onChange({ + name, + filter: filterString, + aggType: agg as CustomMetricAggTypes, + }); + }, + [name, agg, onChange] + ); + + return ( + <> + + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/types.ts b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/types.ts new file mode 100644 index 0000000000000..abc63849ad1d1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/custom_equation/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { AggregationType, IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { MetricExpressionCustomMetric } from '../../../../../common/threshold_rule/types'; +import { MetricExpression } from '../../types'; + +export type CustomMetrics = MetricExpression['customMetrics']; + +export interface AggregationTypes { + [x: string]: AggregationType; +} + +export interface NormalizedField { + name: string; + normalizedType: string; +} + +export type NormalizedFields = NormalizedField[]; + +export interface MetricRowBaseProps { + name: string; + onAdd: () => void; + onDelete: (name: string) => void; + disableDelete: boolean; + disableAdd: boolean; + onChange: (metric: MetricExpressionCustomMetric) => void; + aggregationTypes: AggregationTypes; + errors: IErrorObject; +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression.test.tsx new file mode 100644 index 0000000000000..e87c3041c9284 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` +import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; + +import { Expressions } from './expression'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { Comparator } from '../../../../common/threshold_rule/types'; + +jest.mock('../helpers/source', () => ({ + withSourceProvider: () => jest.fn, + useSourceContext: () => ({ + source: { id: 'default' }, + createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }), + }), +})); + +jest.mock('../../../utils/kibana_react', () => ({ + useKibana: () => ({ + services: mockCoreMock.createStart(), + }), +})); + +const dataViewMock = dataViewPluginMocks.createStartContract(); + +describe('Expression', () => { + async function setup(currentOptions: { + metrics?: MetricsExplorerMetric[]; + filterQuery?: string; + groupBy?: string; + }) { + const ruleParams = { + criteria: [], + groupBy: undefined, + filterQueryText: '', + sourceId: 'default', + }; + const wrapper = mountWithIntl( + Reflect.set(ruleParams, key, value)} + setRuleProperty={() => {}} + metadata={{ + currentOptions, + }} + dataViews={dataViewMock} + /> + ); + + const update = async () => + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + await update(); + + return { wrapper, update, ruleParams }; + } + + it('should prefill the alert using the context metadata', async () => { + const currentOptions = { + groupBy: 'host.hostname', + filterQuery: 'foo', + metrics: [ + { aggregation: 'avg', field: 'system.load.1' }, + { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, + ] as MetricsExplorerMetric[], + }; + const { ruleParams } = await setup(currentOptions); + expect(ruleParams.groupBy).toBe('host.hostname'); + expect(ruleParams.filterQueryText).toBe('foo'); + expect(ruleParams.criteria).toEqual([ + { + metric: 'system.load.1', + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + }, + { + metric: 'system.cpu.user.pct', + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', + aggType: 'cardinality', + }, + ]); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression.tsx new file mode 100644 index 0000000000000..7bc42ccd984ba --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression.tsx @@ -0,0 +1,514 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'; +import { + EuiAccordion, + EuiButtonEmpty, + EuiCheckbox, + EuiFieldSearch, + EuiFormRow, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { debounce } from 'lodash'; +import { + ForLastExpression, + IErrorObject, + RuleTypeParams, + RuleTypeParamsExpressionProps, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '../../../utils/kibana_react'; +import { Aggregators, Comparator, QUERY_INVALID } from '../../../../common/threshold_rule/types'; +import { TimeUnitChar } from '../../../../common/utils/formatters/duration'; +import { AlertContextMeta, AlertParams, MetricExpression } from '../types'; +import { ExpressionChart } from './expression_chart'; +import { ExpressionRow } from './expression_row'; +import { MetricsExplorerKueryBar } from './kuery_bar'; +import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; +import { convertKueryToElasticSearchQuery } from '../helpers/kuery'; +import { useSourceContext, withSourceProvider } from '../helpers/source'; +import { MetricsExplorerGroupBy } from './group_by'; +const FILTER_TYPING_DEBOUNCE_MS = 500; + +type Props = Omit< + RuleTypeParamsExpressionProps, + 'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' | 'onChangeMetaData' +>; + +export const defaultExpression = { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', +} as MetricExpression; + +export function Expressions(props: Props) { + const { setRuleParams, ruleParams, errors, metadata } = props; + const { docLinks } = useKibana().services; + const { source, createDerivedIndexPattern } = useSourceContext(); + + const [timeSize, setTimeSize] = useState(1); + const [timeUnit, setTimeUnit] = useState('m'); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); + + const options = useMemo(() => { + if (metadata?.currentOptions?.metrics) { + return metadata.currentOptions as MetricsExplorerOptions; + } else { + return { + metrics: [], + aggregation: 'avg', + }; + } + }, [metadata]); + + const updateParams = useCallback( + (id, e: MetricExpression) => { + const exp = ruleParams.criteria ? ruleParams.criteria.slice() : []; + exp[id] = e; + setRuleParams('criteria', exp); + }, + [setRuleParams, ruleParams.criteria] + ); + + const addExpression = useCallback(() => { + const exp = ruleParams.criteria?.slice() || []; + exp.push({ + ...defaultExpression, + timeSize: timeSize ?? defaultExpression.timeSize, + timeUnit: timeUnit ?? defaultExpression.timeUnit, + }); + setRuleParams('criteria', exp); + }, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]); + + const removeExpression = useCallback( + (id: number) => { + const exp = ruleParams.criteria?.slice() || []; + if (exp.length > 1) { + exp.splice(id, 1); + setRuleParams('criteria', exp); + } + }, + [setRuleParams, ruleParams.criteria] + ); + + const onFilterChange = useCallback( + (filter: any) => { + setRuleParams('filterQueryText', filter); + try { + setRuleParams( + 'filterQuery', + convertKueryToElasticSearchQuery(filter, derivedIndexPattern, false) || '' + ); + } catch (e) { + setRuleParams('filterQuery', QUERY_INVALID); + } + }, + [setRuleParams, derivedIndexPattern] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ + onFilterChange, + ]); + + const onGroupByChange = useCallback( + (group: string | null | string[]) => { + setRuleParams('groupBy', group && group.length ? group : ''); + }, + [setRuleParams] + ); + + const emptyError = useMemo(() => { + return { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + }; + }, []); + + const updateTimeSize = useCallback( + (ts: number | undefined) => { + const criteria = + ruleParams.criteria?.map((c) => ({ + ...c, + timeSize: ts, + })) || []; + setTimeSize(ts || undefined); + setRuleParams('criteria', criteria); + }, + [ruleParams.criteria, setRuleParams] + ); + + const updateTimeUnit = useCallback( + (tu: string) => { + const criteria = + ruleParams.criteria?.map((c) => ({ + ...c, + timeUnit: tu, + })) || []; + setTimeUnit(tu as TimeUnitChar); + setRuleParams('criteria', criteria as AlertParams['criteria']); + }, + [ruleParams.criteria, setRuleParams] + ); + + const preFillAlertCriteria = useCallback(() => { + const md = metadata; + if (md?.currentOptions?.metrics?.length) { + setRuleParams( + 'criteria', + md.currentOptions.metrics.map((metric) => ({ + metric: metric.field, + comparator: Comparator.GT, + threshold: [], + timeSize, + timeUnit, + aggType: metric.aggregation, + })) as AlertParams['criteria'] + ); + } else { + setRuleParams('criteria', [defaultExpression]); + } + }, [metadata, setRuleParams, timeSize, timeUnit]); + + const preFillAlertFilter = useCallback(() => { + const md = metadata; + if (md && md.currentOptions?.filterQuery) { + setRuleParams('filterQueryText', md.currentOptions.filterQuery); + setRuleParams( + 'filterQuery', + convertKueryToElasticSearchQuery(md.currentOptions.filterQuery, derivedIndexPattern) || '' + ); + } else if (md && md.currentOptions?.groupBy && md.series) { + const { groupBy } = md.currentOptions; + const filter = Array.isArray(groupBy) + ? groupBy.map((field, index) => `${field}: "${md.series?.keys?.[index]}"`).join(' and ') + : `${groupBy}: "${md.series.id}"`; + setRuleParams('filterQueryText', filter); + setRuleParams( + 'filterQuery', + convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || '' + ); + } + }, [metadata, derivedIndexPattern, setRuleParams]); + + const preFillAlertGroupBy = useCallback(() => { + const md = metadata; + if (md && md.currentOptions?.groupBy && !md.series) { + setRuleParams('groupBy', md.currentOptions.groupBy); + } + }, [metadata, setRuleParams]); + + useEffect(() => { + if (ruleParams.criteria && ruleParams.criteria.length) { + setTimeSize(ruleParams.criteria[0].timeSize); + setTimeUnit(ruleParams.criteria[0].timeUnit); + } else { + preFillAlertCriteria(); + } + + if (!ruleParams.filterQuery) { + preFillAlertFilter(); + } + + if (!ruleParams.groupBy) { + preFillAlertGroupBy(); + } + + if (!ruleParams.sourceId) { + setRuleParams('sourceId', source?.id || 'default'); + } + + if (typeof ruleParams.alertOnNoData === 'undefined') { + setRuleParams('alertOnNoData', true); + } + if (typeof ruleParams.alertOnGroupDisappear === 'undefined') { + setRuleParams('alertOnGroupDisappear', true); + } + }, [metadata, source]); // eslint-disable-line react-hooks/exhaustive-deps + + const handleFieldSearchChange = useCallback( + (e: ChangeEvent) => onFilterChange(e.target.value), + [onFilterChange] + ); + + const hasGroupBy = useMemo( + () => ruleParams.groupBy && ruleParams.groupBy.length > 0, + [ruleParams.groupBy] + ); + + const disableNoData = useMemo( + () => ruleParams.criteria?.every((c) => c.aggType === Aggregators.COUNT), + [ruleParams.criteria] + ); + + // Test to see if any of the group fields in groupBy are already filtered down to a single + // group by the filterQuery. If this is the case, then a groupBy is unnecessary, as it would only + // ever produce one group instance + const groupByFilterTestPatterns = useMemo(() => { + if (!ruleParams.groupBy) return null; + const groups = !Array.isArray(ruleParams.groupBy) ? [ruleParams.groupBy] : ruleParams.groupBy; + return groups.map((group: string) => ({ + groupName: group, + pattern: new RegExp(`{"match(_phrase)?":{"${group}":"(.*?)"}}`), + })); + }, [ruleParams.groupBy]); + + const redundantFilterGroupBy = useMemo(() => { + const { filterQuery } = ruleParams; + if (typeof filterQuery !== 'string' || !groupByFilterTestPatterns) return []; + return groupByFilterTestPatterns + .map(({ groupName, pattern }) => { + if (pattern.test(filterQuery)) { + return groupName; + } + }) + .filter((g) => typeof g === 'string') as string[]; + }, [ruleParams, groupByFilterTestPatterns]); + + return ( + <> + + +

    + +

    +
    + + {ruleParams.criteria && + ruleParams.criteria.map((e, idx) => { + return ( + 1) || false} + fields={derivedIndexPattern.fields} + remove={removeExpression} + addExpression={addExpression} + key={idx} // idx's don't usually make good key's but here the index has semantic meaning + expressionId={idx} + setRuleParams={updateParams} + errors={(errors[idx] as IErrorObject) || emptyError} + expression={e || {}} + dataView={derivedIndexPattern} + > + + + ); + })} + +
    + +
    + + +
    + + + +
    + + + + + + {i18n.translate('xpack.observability.threshold.rule.alertFlyout.alertOnNoData', { + defaultMessage: "Alert me if there's no data", + })}{' '} + + + + + } + checked={ruleParams.alertOnNoData} + onChange={(e) => setRuleParams('alertOnNoData', e.target.checked)} + /> + + + + + + {(metadata && ( + + )) || ( + + )} + + + + + + + {redundantFilterGroupBy.length > 0 && ( + <> + + + {redundantFilterGroupBy.join(', ')}, + groupCount: redundantFilterGroupBy.length, + filteringAndGroupingLink: ( + + {i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink', + { defaultMessage: 'the docs' } + )} + + ), + }} + /> + + + )} + + + {i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear', + { + defaultMessage: 'Alert me if a group stops reporting data', + } + )}{' '} + + + + + } + disabled={disableNoData || !hasGroupBy} + checked={Boolean(hasGroupBy && ruleParams.alertOnGroupDisappear)} + onChange={(e) => setRuleParams('alertOnGroupDisappear', e.target.checked)} + /> + + + ); +} + +const docCountNoDataDisabledHelpText = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText', + { + defaultMessage: '[This setting is not applicable to the Document Count aggregator.]', + } +); + +// required for dynamic import +// eslint-disable-next-line import/no-default-export +export default withSourceProvider(Expressions)('default'); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.test.tsx new file mode 100644 index 0000000000000..88d57b8688972 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.test.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ReactElement } from 'react'; +import { act } from 'react-dom/test-utils'; +import { LineAnnotation, RectAnnotation } from '@elastic/charts'; +import { DataViewBase } from '@kbn/es-query'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` +import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; +import { MetricExpression } from '../types'; +import { ExpressionChart } from './expression_chart'; +import { + Aggregators, + Comparator, + MetricsSourceConfiguration, +} from '../../../../common/threshold_rule/types'; + +const mockStartServices = mockCoreMock.createStart(); + +jest.mock('../../../utils/kibana_react', () => ({ + useKibana: () => ({ + services: { + ...mockStartServices, + charts: { + activeCursor: jest.fn(), + }, + }, + }), +})); + +const mockResponse = { + pageInfo: { + afterKey: null, + total: 0, + }, + series: [{ id: 'Everything', rows: [], columns: [] }], +}; + +jest.mock('../hooks/use_metrics_explorer_chart_data', () => ({ + useMetricsExplorerChartData: () => ({ loading: false, data: { pages: [mockResponse] } }), +})); + +describe('ExpressionChart', () => { + async function setup( + expression: MetricExpression, + filterQuery?: string, + groupBy?: string, + annotations?: Array> + ) { + const derivedIndexPattern: DataViewBase = { + title: 'metricbeat-*', + fields: [], + }; + + const source: MetricsSourceConfiguration = { + id: 'default', + origin: 'fallback', + configuration: { + name: 'default', + description: 'The default configuration', + metricAlias: 'metricbeat-*', + inventoryDefaultView: 'host', + metricsExplorerDefaultView: 'host', + anomalyThreshold: 20, + }, + }; + + const wrapper = mountWithIntl( + + ); + + const update = async () => + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + await update(); + + return { wrapper, update }; + } + + it('should display no data message', async () => { + const expression: MetricExpression = { + aggType: Aggregators.AVERAGE, + timeSize: 1, + timeUnit: 'm', + sourceId: 'default', + threshold: [1], + comparator: Comparator.GT_OR_EQ, + }; + const { wrapper } = await setup(expression); + expect(wrapper.find('[data-test-subj~="noChartData"]').exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.tsx new file mode 100644 index 0000000000000..bab6f02ceb340 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression_chart.tsx @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, { ReactElement, useRef } from 'react'; +import { + Axis, + Chart, + LineAnnotation, + niceTimeFormatter, + Position, + RectAnnotation, + Settings, +} from '@elastic/charts'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useActiveCursor } from '@kbn/charts-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; +import { first, last } from 'lodash'; +import moment from 'moment'; +import { useKibana } from '../../../utils/kibana_react'; +import { + MetricsExplorerAggregation, + MetricsExplorerRow, +} from '../../../../common/threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/threshold_rule/color_palette'; +import { + MetricsExplorerChartType, + MetricsExplorerOptionsMetric, + MetricsSourceConfiguration, +} from '../../../../common/threshold_rule/types'; +import { MetricExpression, TimeRange } from '../types'; +import { createFormatterForMetric } from '../helpers/create_formatter_for_metric'; +import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; +import { + ChartContainer, + LoadingState, + NoDataState, + TIME_LABELS, + getChartTheme, +} from './criterion_preview_chart/criterion_preview_chart'; +import { ThresholdAnnotations } from './criterion_preview_chart/threshold_annotations'; +import { CUSTOM_EQUATION } from '../i18n_strings'; +import { calculateDomain } from '../helpers/calculate_domain'; +import { getMetricId } from '../helpers/get_metric_id'; +import { MetricExplorerSeriesChart } from './series_chart'; + +interface Props { + expression: MetricExpression; + derivedIndexPattern: DataViewBase; + annotations?: Array>; + chartType?: MetricsExplorerChartType; + filterQuery?: string; + groupBy?: string | string[]; + hideTitle?: boolean; + source?: MetricsSourceConfiguration; + timeRange?: TimeRange; +} + +export function ExpressionChart({ + expression, + derivedIndexPattern, + annotations, + chartType = MetricsExplorerChartType.bar, + filterQuery, + groupBy, + hideTitle = false, + source, + timeRange, +}: Props) { + const { charts, uiSettings } = useKibana().services; + const { isLoading, data } = useMetricsExplorerChartData( + expression, + derivedIndexPattern, + source, + filterQuery, + groupBy, + timeRange + ); + + const chartRef = useRef(null); + const handleCursorUpdate = useActiveCursor(charts.activeCursor, chartRef, { + isDateHistogram: true, + }); + + if (isLoading) { + return ; + } + + if (!data) { + return ; + } + + const isDarkMode = uiSettings?.get('theme:darkMode') || false; + const firstSeries = first(first(data.pages)!.series); + // Creating a custom series where the ID is changed to 0 + // so that we can get a proper domain + if (!firstSeries || !firstSeries.rows || firstSeries.rows.length === 0) { + return ; + } + + const firstTimestamp = first(firstSeries.rows)!.timestamp; + const lastTimestamp = last(firstSeries.rows)!.timestamp; + const metric: MetricsExplorerOptionsMetric = { + field: expression.metric, + aggregation: expression.aggType as MetricsExplorerAggregation, + color: Color.color0, + }; + + if (metric.aggregation === 'custom') { + metric.label = expression.label || CUSTOM_EQUATION; + } + + const dateFormatter = + firstTimestamp == null || lastTimestamp == null + ? (value: number) => `${value}` + : niceTimeFormatter([firstTimestamp, lastTimestamp]); + + const criticalThresholds = expression.threshold.slice().sort(); + const warningThresholds = expression.warningThreshold?.slice().sort() ?? []; + const thresholds = [...criticalThresholds, ...warningThresholds].sort(); + + const series = { + ...firstSeries, + rows: firstSeries.rows.map((row) => { + const newRow: MetricsExplorerRow = { ...row }; + thresholds.forEach((thresholdValue, index) => { + newRow[getMetricId(metric, `threshold_${index}`)] = thresholdValue; + }); + return newRow; + }), + }; + + const dataDomain = calculateDomain(series, [metric], false); + const domain = { + max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, + min: Math.min(dataDomain.min, first(thresholds) || dataDomain.min) * 0.9, // add 10% floor, + }; + + if (domain.min === first(expression.threshold)) { + domain.min = domain.min * 0.9; + } + + const { timeSize, timeUnit } = expression; + const timeLabel = TIME_LABELS[timeUnit as keyof typeof TIME_LABELS]; + + return ( + <> + + + + + {expression.warningComparator && expression.warningThreshold && ( + + )} + {annotations} + + + + moment(value).format(uiSettings.get(UI_SETTINGS.DATE_FORMAT)), + }} + externalPointerEvents={{ + tooltip: { visible: true }, + }} + theme={getChartTheme(isDarkMode)} + /> + + + {!hideTitle && ( +
    + {series.id !== 'ALL' ? ( + + + + ) : ( + + + + )} +
    + )} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression_row.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression_row.test.tsx new file mode 100644 index 0000000000000..83774517feef1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression_row.test.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Comparator } from '../../../../common/threshold_rule/types'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { MetricExpression } from '../types'; +import { ExpressionRow } from './expression_row'; + +jest.mock('../helpers/source', () => ({ + withSourceProvider: () => jest.fn, + useSourceContext: () => ({ + source: { id: 'default' }, + createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }), + }), +})); + +describe('ExpressionRow', () => { + async function setup(expression: MetricExpression) { + const wrapper = mountWithIntl( + {}} + addExpression={() => {}} + key={1} + expressionId={1} + setRuleParams={() => {}} + errors={{ + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + }} + expression={expression} + dataView={{ fields: [], title: 'metricbeat-*' }} + /> + ); + + const update = async () => + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + await update(); + + return { wrapper, update }; + } + + it('should display thresholds as a percentage for pct metrics', async () => { + const expression = { + metric: 'system.cpu.user.pct', + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + }; + const { wrapper, update } = await setup(expression as MetricExpression); + await update(); + const [valueMatch] = + wrapper + .html() + .match('50') ?? + []; + expect(valueMatch).toBeTruthy(); + }); + + it('should display thresholds as a decimal for all other metrics', async () => { + const expression = { + metric: 'system.load.1', + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + }; + const { wrapper } = await setup(expression as MetricExpression); + const [valueMatch] = + wrapper + .html() + .match('0.5') ?? + []; + expect(valueMatch).toBeTruthy(); + }); + + it('should render a helpText for the of expression', async () => { + const expression = { + metric: 'system.load.1', + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + } as MetricExpression; + + const { wrapper } = await setup(expression as MetricExpression); + + const helpText = wrapper.find('[data-test-subj="ofExpression"]').at(0).prop('helpText'); + + expect(helpText).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/pages/threshold/components/expression_row.tsx new file mode 100644 index 0000000000000..2050d90d3eb0d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/expression_row.tsx @@ -0,0 +1,508 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiExpression, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { omit } from 'lodash'; +import React, { useCallback, useMemo, useState } from 'react'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { + AggregationType, + builtInComparators, + IErrorObject, + OfExpression, + ThresholdExpression, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; +import useToggle from 'react-use/lib/useToggle'; +import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { AGGREGATION_TYPES, DerivedIndexPattern, MetricExpression } from '../types'; +import { CustomEquationEditor } from './custom_equation'; +import { CUSTOM_EQUATION } from '../i18n_strings'; +import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert'; + +const customComparators = { + ...builtInComparators, + [Comparator.OUTSIDE_RANGE]: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel', { + defaultMessage: 'Is not between', + }), + value: Comparator.OUTSIDE_RANGE, + requiredValues: 2, + }, +}; + +interface ExpressionRowProps { + fields: DerivedIndexPattern['fields']; + expressionId: number; + expression: MetricExpression; + errors: IErrorObject; + canDelete: boolean; + addExpression(): void; + remove(id: number): void; + setRuleParams(id: number, params: MetricExpression): void; + dataView: DataViewBase; +} + +const StyledExpressionRow = euiStyled(EuiFlexGroup)` + display: flex; + flex-wrap: wrap; + align-items: center; + margin: 0 -4px; +`; + +const StyledExpression = euiStyled.div` + padding: 0 4px; +`; + +const StyledHealth = euiStyled(EuiHealth)` + margin-left: 4px; +`; + +// eslint-disable-next-line react/function-component-definition +export const ExpressionRow: React.FC = (props) => { + const [isExpanded, toggle] = useToggle(true); + + const { + dataView, + children, + setRuleParams, + expression, + errors, + expressionId, + remove, + fields, + canDelete, + } = props; + + const { + aggType = AGGREGATION_TYPES.MAX, + metric, + comparator = Comparator.GT, + threshold = [], + warningThreshold = [], + warningComparator, + } = expression; + const [displayWarningThreshold, setDisplayWarningThreshold] = useState( + Boolean(warningThreshold?.length) + ); + + const isMetricPct = useMemo(() => Boolean(metric && metric.endsWith('.pct')), [metric]); + + const updateMetric = useCallback( + (m?: MetricExpression['metric']) => { + setRuleParams(expressionId, { ...expression, metric: m }); + }, + [expressionId, expression, setRuleParams] + ); + + const updateComparator = useCallback( + (c?: string) => { + setRuleParams(expressionId, { ...expression, comparator: c as Comparator }); + }, + [expressionId, expression, setRuleParams] + ); + + const updateWarningComparator = useCallback( + (c?: string) => { + setRuleParams(expressionId, { ...expression, warningComparator: c as Comparator }); + }, + [expressionId, expression, setRuleParams] + ); + + const convertThreshold = useCallback( + (enteredThreshold) => + isMetricPct ? enteredThreshold.map((v: number) => pctToDecimal(v)) : enteredThreshold, + [isMetricPct] + ); + + const updateThreshold = useCallback( + (enteredThreshold) => { + const t = convertThreshold(enteredThreshold); + if (t.join() !== expression.threshold.join()) { + setRuleParams(expressionId, { ...expression, threshold: t }); + } + }, + [expressionId, expression, convertThreshold, setRuleParams] + ); + + const updateWarningThreshold = useCallback( + (enteredThreshold) => { + const t = convertThreshold(enteredThreshold); + if (t.join() !== expression.warningThreshold?.join()) { + setRuleParams(expressionId, { ...expression, warningThreshold: t }); + } + }, + [expressionId, expression, convertThreshold, setRuleParams] + ); + + const toggleWarningThreshold = useCallback(() => { + if (!displayWarningThreshold) { + setDisplayWarningThreshold(true); + setRuleParams(expressionId, { + ...expression, + warningComparator: comparator, + warningThreshold: [], + }); + } else { + setDisplayWarningThreshold(false); + setRuleParams(expressionId, omit(expression, 'warningComparator', 'warningThreshold')); + } + }, [ + displayWarningThreshold, + setDisplayWarningThreshold, + setRuleParams, + comparator, + expression, + expressionId, + ]); + + const handleCustomMetricChange = useCallback( + (exp) => { + setRuleParams(expressionId, exp); + }, + [expressionId, setRuleParams] + ); + + const criticalThresholdExpression = ( + + ); + + const warningThresholdExpression = displayWarningThreshold && ( + + ); + + const normalizedFields = fields.map((f) => ({ + normalizedType: f.type, + name: f.name, + })); + + // for v8.9 we want to show only the Custom Equation. Use EuiExpression instead of WhenExpression */ + // const updateAggType = useCallback( + // (at: string) => { + // setRuleParams(expressionId, { + // ...expression, + // aggType: at as MetricExpression['aggType'], + // metric: ['custom', 'count'].includes(at) ? undefined : expression.metric, + // customMetrics: at === 'custom' ? expression.customMetrics : undefined, + // equation: at === 'custom' ? expression.equation : undefined, + // label: at === 'custom' ? expression.label : undefined, + // }); + // }, + // [expressionId, expression, setRuleParams] + // ); + return ( + <> + + + + + + + + {/* for v8.9 we want to show only the Custom Equation. Use EuiExpression instead of WhenExpression */} + {/* */} + + + {!['count', 'custom'].includes(aggType) && ( + + + + + ), + }} + /> + } + data-test-subj="ofExpression" + /> + + )} + {!displayWarningThreshold && criticalThresholdExpression} + {!displayWarningThreshold && ( + <> + + + + + + + + )} + + {displayWarningThreshold && ( + <> + + {criticalThresholdExpression} + + + + + + {warningThresholdExpression} + + + + + + + )} + {aggType === Aggregators.CUSTOM && ( + <> + + + + + + + )} + + {canDelete && ( + + remove(expressionId)} + /> + + )} + + {isExpanded ?
    {children}
    : null} + + + ); +}; + +const ThresholdElement: React.FC<{ + updateComparator: (c?: string) => void; + updateThreshold: (t?: number[]) => void; + threshold: MetricExpression['threshold']; + isMetricPct: boolean; + comparator: MetricExpression['comparator']; + errors: IErrorObject; + // eslint-disable-next-line react/function-component-definition +}> = ({ updateComparator, updateThreshold, threshold, isMetricPct, comparator, errors }) => { + const displayedThreshold = useMemo(() => { + if (isMetricPct) return threshold.map((v) => decimalToPct(v)); + return threshold; + }, [threshold, isMetricPct]); + + return ( + <> + + + + {isMetricPct && ( +
    + % +
    + )} + + ); +}; + +export const aggregationType: { [key: string]: AggregationType } = { + avg: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.avg', { + defaultMessage: 'Average', + }), + fieldRequired: true, + validNormalizedTypes: ['number', 'histogram'], + value: AGGREGATION_TYPES.AVERAGE, + }, + max: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.max', { + defaultMessage: 'Max', + }), + fieldRequired: true, + validNormalizedTypes: ['number', 'date', 'histogram'], + value: AGGREGATION_TYPES.MAX, + }, + min: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.min', { + defaultMessage: 'Min', + }), + fieldRequired: true, + validNormalizedTypes: ['number', 'date', 'histogram'], + value: AGGREGATION_TYPES.MIN, + }, + cardinality: { + text: i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality', + { + defaultMessage: 'Cardinality', + } + ), + fieldRequired: false, + value: AGGREGATION_TYPES.CARDINALITY, + validNormalizedTypes: ['number', 'string', 'ip', 'date'], + }, + rate: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.rate', { + defaultMessage: 'Rate', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.RATE, + validNormalizedTypes: ['number'], + }, + count: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.count', { + defaultMessage: 'Document count', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.COUNT, + validNormalizedTypes: ['number'], + }, + sum: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.sum', { + defaultMessage: 'Sum', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.SUM, + validNormalizedTypes: ['number', 'histogram'], + }, + p95: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.p95', { + defaultMessage: '95th Percentile', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.P95, + validNormalizedTypes: ['number', 'histogram'], + }, + p99: { + text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.aggregationText.p99', { + defaultMessage: '99th Percentile', + }), + fieldRequired: false, + value: AGGREGATION_TYPES.P99, + validNormalizedTypes: ['number', 'histogram'], + }, + custom: { + text: CUSTOM_EQUATION, + fieldRequired: false, + value: AGGREGATION_TYPES.CUSTOM, + validNormalizedTypes: ['number', 'histogram'], + }, +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/group_by.tsx b/x-pack/plugins/observability/public/pages/threshold/components/group_by.tsx new file mode 100644 index 0000000000000..6e3573f01b6a3 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/group_by.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 { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import React, { useCallback } from 'react'; +import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; +import { DerivedIndexPattern } from '../types'; + +interface Props { + options: MetricsExplorerOptions; + onChange: (groupBy: string | null | string[]) => void; + fields: DerivedIndexPattern['fields']; + errorOptions?: string[]; +} + +export function MetricsExplorerGroupBy({ options, onChange, fields, errorOptions }: Props) { + const handleChange = useCallback( + (selectedOptions: Array<{ label: string }>) => { + const groupBy = selectedOptions.map((option) => option.label); + onChange(groupBy); + }, + [onChange] + ); + + const selectedOptions = Array.isArray(options.groupBy) + ? options.groupBy.map((field) => ({ + label: field, + color: errorOptions?.includes(field) ? 'danger' : undefined, + })) + : options.groupBy + ? [ + { + label: options.groupBy, + color: errorOptions?.includes(options.groupBy) ? 'danger' : undefined, + }, + ] + : []; + + return ( + f.aggregatable && f.type === 'string') + .map((f) => ({ label: f.name }))} + onChange={handleChange} + isClearable={true} + /> + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/kuery_bar.tsx b/x-pack/plugins/observability/public/pages/threshold/components/kuery_bar.tsx new file mode 100644 index 0000000000000..18dea924489a5 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/kuery_bar.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { fromKueryExpression } from '@kbn/es-query'; +import React, { useEffect, useState } from 'react'; +import { DataViewBase } from '@kbn/es-query'; +import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; + +import { WithKueryAutocompletion } from '../containers/with_kuery_autocompletion'; +import { AutocompleteField } from './autocomplete_field'; + +type LoadSuggestionsFn = ( + e: string, + p: number, + m?: number, + transform?: (s: QuerySuggestion[]) => QuerySuggestion[] +) => void; +export type CurryLoadSuggestionsType = (loadSuggestions: LoadSuggestionsFn) => LoadSuggestionsFn; + +interface Props { + derivedIndexPattern: DataViewBase; + onSubmit: (query: string) => void; + onChange?: (query: string) => void; + value?: string | null; + placeholder?: string; + curryLoadSuggestions?: CurryLoadSuggestionsType; + compressed?: boolean; +} + +function validateQuery(query: string) { + try { + fromKueryExpression(query); + } catch (err) { + return false; + } + return true; +} + +export function MetricsExplorerKueryBar({ + derivedIndexPattern, + onSubmit, + onChange, + value, + placeholder, + curryLoadSuggestions = defaultCurryLoadSuggestions, + compressed, +}: Props) { + const [draftQuery, setDraftQuery] = useState(value || ''); + const [isValid, setValidation] = useState(true); + + // This ensures that if value changes out side this component it will update. + useEffect(() => { + if (value) { + setDraftQuery(value); + } + }, [value]); + + const handleChange = (query: string) => { + setValidation(validateQuery(query)); + setDraftQuery(query); + if (onChange) { + onChange(query); + } + }; + + const filteredDerivedIndexPattern = { + ...derivedIndexPattern, + fields: derivedIndexPattern.fields, + }; + + const defaultPlaceholder = i18n.translate( + 'xpack.observability.threshold.rule.homePage.toolbar.kqlSearchFieldPlaceholder', + { + defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)', + } + ); + + return ( + + {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( + + )} + + ); +} + +const defaultCurryLoadSuggestions: CurryLoadSuggestionsType = + (loadSuggestions) => + (...args) => + loadSuggestions(...args); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/metrics_alert_dropdown.tsx b/x-pack/plugins/observability/public/pages/threshold/components/metrics_alert_dropdown.tsx new file mode 100644 index 0000000000000..be29390e48bd1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/metrics_alert_dropdown.tsx @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState, useCallback, useMemo } from 'react'; +import { + EuiPopover, + EuiHeaderLink, + EuiContextMenu, + EuiContextMenuPanelDescriptor, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { InfraClientStartDeps } from '../types'; +import { PrefilledThresholdAlertFlyout } from './alert_flyout'; + +type VisibleFlyoutType = 'inventory' | 'threshold' | null; + +export function MetricsAlertDropdown() { + const [popoverOpen, setPopoverOpen] = useState(false); + const [visibleFlyoutType, setVisibleFlyoutType] = useState(null); + const uiCapabilities = useKibana().services.application?.capabilities; + const { + services: { observability }, + } = useKibana(); + const canCreateAlerts = useMemo( + () => Boolean(uiCapabilities?.infrastructure?.save), + [uiCapabilities] + ); + + const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]); + + const closePopover = useCallback(() => { + setPopoverOpen(false); + }, [setPopoverOpen]); + + const togglePopover = useCallback(() => { + setPopoverOpen(!popoverOpen); + }, [setPopoverOpen, popoverOpen]); + const infrastructureAlertsPanel = useMemo( + () => ({ + id: 1, + title: i18n.translate('xpack.observability.threshold.rule.infrastructureDropdownTitle', { + defaultMessage: 'Infrastructure rules', + }), + items: [ + { + 'data-test-subj': 'inventory-alerts-create-rule', + name: i18n.translate('xpack.observability.threshold.rule.createInventoryRuleButton', { + defaultMessage: 'Create inventory rule', + }), + onClick: () => { + closePopover(); + setVisibleFlyoutType('inventory'); + }, + }, + ], + }), + [setVisibleFlyoutType, closePopover] + ); + + const metricsAlertsPanel = useMemo( + () => ({ + id: 2, + title: i18n.translate('xpack.observability.threshold.rule.metricsDropdownTitle', { + defaultMessage: 'Metrics rules', + }), + items: [ + { + 'data-test-subj': 'metrics-threshold-alerts-create-rule', + name: i18n.translate('xpack.observability.threshold.rule.createThresholdRuleButton', { + defaultMessage: 'Create threshold rule', + }), + onClick: () => { + closePopover(); + setVisibleFlyoutType('threshold'); + }, + }, + ], + }), + [setVisibleFlyoutType, closePopover] + ); + + const manageRulesLinkProps = observability.useRulesLink(); + + const manageAlertsMenuItem = useMemo( + () => ({ + name: i18n.translate('xpack.observability.threshold.rule.manageRules', { + defaultMessage: 'Manage rules', + }), + icon: 'tableOfContents', + onClick: manageRulesLinkProps.onClick, + }), + [manageRulesLinkProps] + ); + + const firstPanelMenuItems: EuiContextMenuPanelDescriptor['items'] = useMemo( + () => + canCreateAlerts + ? [ + { + 'data-test-subj': 'inventory-alerts-menu-option', + name: i18n.translate( + 'xpack.observability.threshold.rule.infrastructureDropdownMenu', + { + defaultMessage: 'Infrastructure', + } + ), + panel: 1, + }, + { + 'data-test-subj': 'metrics-threshold-alerts-menu-option', + name: i18n.translate('xpack.observability.threshold.rule.metricsDropdownMenu', { + defaultMessage: 'Metrics', + }), + panel: 2, + }, + manageAlertsMenuItem, + ] + : [manageAlertsMenuItem], + [canCreateAlerts, manageAlertsMenuItem] + ); + + const panels: EuiContextMenuPanelDescriptor[] = useMemo( + () => + [ + { + id: 0, + title: i18n.translate('xpack.observability.threshold.rule.alertDropdownTitle', { + defaultMessage: 'Alerts and rules', + }), + items: firstPanelMenuItems, + }, + ].concat(canCreateAlerts ? [infrastructureAlertsPanel, metricsAlertsPanel] : []), + [infrastructureAlertsPanel, metricsAlertsPanel, firstPanelMenuItems, canCreateAlerts] + ); + + return ( + <> + + + + } + isOpen={popoverOpen} + closePopover={closePopover} + > + + + + + ); +} + +interface AlertFlyoutProps { + visibleFlyoutType: VisibleFlyoutType; + onClose(): void; +} + +function AlertFlyout({ visibleFlyoutType, onClose }: AlertFlyoutProps) { + switch (visibleFlyoutType) { + case 'threshold': + return ; + default: + return null; + } +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/series_chart.tsx b/x-pack/plugins/observability/public/pages/threshold/components/series_chart.tsx new file mode 100644 index 0000000000000..c7716c474b22c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/series_chart.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + ScaleType, + AreaSeries, + BarSeries, + RecursivePartial, + AreaSeriesStyle, + BarSeriesStyle, +} from '@elastic/charts'; +import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; +import { Color, colorTransformer } from '../../../../common/threshold_rule/color_palette'; +import { + MetricsExplorerChartType, + MetricsExplorerOptionsMetric, +} from '../../../../common/threshold_rule/types'; + +import { getMetricId } from '../helpers/get_metric_id'; +import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_time_zone_setting'; +import { createMetricLabel } from '../helpers/create_metric_label'; + +type NumberOrString = string | number; + +interface Props { + metric: MetricsExplorerOptionsMetric; + id: NumberOrString | NumberOrString[]; + series: MetricsExplorerSeries; + type: MetricsExplorerChartType; + stack: boolean; + opacity?: number; +} + +export function MetricExplorerSeriesChart(props: Props) { + if (MetricsExplorerChartType.bar === props.type) { + return ; + } + return ; +} + +export function MetricsExplorerAreaChart({ metric, id, series, type, stack, opacity }: Props) { + const timezone = useKibanaTimeZoneSetting(); + const color = (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0); + + const yAccessors = Array.isArray(id) + ? id.map((i) => getMetricId(metric, i)).slice(id.length - 1, id.length) + : [getMetricId(metric, id)]; + const y0Accessors = + Array.isArray(id) && id.length > 1 + ? id.map((i) => getMetricId(metric, i)).slice(0, 1) + : undefined; + const chartId = `series-${series.id}-${yAccessors.join('-')}`; + + const seriesAreaStyle: RecursivePartial = { + line: { + strokeWidth: 2, + visible: true, + }, + area: { + opacity: opacity || 0.5, + visible: type === MetricsExplorerChartType.area, + }, + }; + + return ( + + ); +} + +export function MetricsExplorerBarChart({ metric, id, series, stack }: Props) { + const timezone = useKibanaTimeZoneSetting(); + const color = (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0); + + const yAccessors = Array.isArray(id) + ? id.map((i) => getMetricId(metric, i)).slice(id.length - 1, id.length) + : [getMetricId(metric, id)]; + const chartId = `series-${series.id}-${yAccessors.join('-')}`; + + const seriesBarStyle: RecursivePartial = { + rectBorder: { + stroke: color, + strokeWidth: 1, + visible: true, + }, + rect: { + opacity: 1, + }, + }; + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/threshold.stories.tsx b/x-pack/plugins/observability/public/pages/threshold/components/threshold.stories.tsx new file mode 100644 index 0000000000000..38967bc817600 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/threshold.stories.tsx @@ -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 React from 'react'; +import { ComponentMeta } from '@storybook/react'; +import { LIGHT_THEME } from '@elastic/charts'; +import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; + +import { Comparator } from '../../../../common/threshold_rule/types'; +import { Props, Threshold as Component } from './threshold'; + +export default { + component: Component, + title: 'infra/alerting/Threshold', + decorators: [ + (Story) => ( +
    + {Story()} +
    + ), + ], +} as ComponentMeta; + +const defaultProps: Props = { + chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, + comparator: Comparator.GT, + id: 'componentId', + threshold: 90, + title: 'Threshold breached', + value: 93, + valueFormatter: (d) => `${d}%`, +}; + +export const Default = { + args: { + ...defaultProps, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/components/threshold.test.tsx b/x-pack/plugins/observability/public/pages/threshold/components/threshold.test.tsx new file mode 100644 index 0000000000000..f0e222da4e857 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/threshold.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LIGHT_THEME } from '@elastic/charts'; +import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; + +import { render } from '@testing-library/react'; +import { Props, Threshold } from './threshold'; +import React from 'react'; +import { Comparator } from '../../../../common/threshold_rule/types'; + +describe('Threshold', () => { + const renderComponent = (props: Partial = {}) => { + const defaultProps: Props = { + chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, + comparator: Comparator.GT, + id: 'componentId', + threshold: 90, + title: 'Threshold breached', + value: 93, + valueFormatter: (d) => `${d}%`, + }; + + return render( +
    + +
    + ); + }; + + it('shows component', () => { + const component = renderComponent(); + expect(component.queryByTestId('threshold-90-93')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/threshold.tsx b/x-pack/plugins/observability/public/pages/threshold/components/threshold.tsx new file mode 100644 index 0000000000000..aad33f1c0c654 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/threshold.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Chart, Metric, Settings } from '@elastic/charts'; +import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; +import type { PartialTheme, Theme } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import { Comparator } from '../../../../common/threshold_rule/types'; + +export interface ChartProps { + theme: PartialTheme; + baseTheme: Theme; +} + +export interface Props { + chartProps: ChartProps; + comparator: Comparator | string; + id: string; + threshold: number; + title: string; + value: number; + valueFormatter: (d: number) => string; +} + +export function Threshold({ + chartProps: { theme, baseTheme }, + comparator, + id, + threshold, + title, + value, + valueFormatter, +}: Props) { + const color = useEuiBackgroundColor('danger'); + + return ( + + + + + {i18n.translate('xpack.observability.threshold.rule.thresholdExtraTitle', { + values: { comparator, threshold: valueFormatter(threshold) }, + defaultMessage: `Alert when {comparator} {threshold}`, + })} + + ), + color, + value, + valueFormatter, + icon: ({ width, height, color: iconColor }) => ( + + ), + }, + ], + ]} + /> + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/triggers_actions_context.tsx b/x-pack/plugins/observability/public/pages/threshold/components/triggers_actions_context.tsx new file mode 100644 index 0000000000000..091f28a151d64 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/triggers_actions_context.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; + +interface ContextProps { + triggersActionsUI: TriggersAndActionsUIPublicPluginStart | null; +} + +export const TriggerActionsContext = React.createContext({ + triggersActionsUI: null, +}); + +interface Props { + triggersActionsUI: TriggersAndActionsUIPublicPluginStart; + children: React.ReactNode; +} + +export function TriggersActionsProvider(props: Props) { + return ( + + {props.children} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/threshold/components/validation.test.ts b/x-pack/plugins/observability/public/pages/threshold/components/validation.test.ts new file mode 100644 index 0000000000000..ac1b545e4a302 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/validation.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 { EQUATION_REGEX } from './validation'; + +describe('Metric Threshold Validation', () => { + describe('valid equations', () => { + const validExpression = [ + '(A + B) / 100', + '(A - B) * 100', + 'A > 1 ? A : B', + 'A <= 1 ? A : B', + 'A && B || C', + ]; + validExpression.forEach((exp) => { + it(exp, () => { + expect(exp.match(EQUATION_REGEX)).toBeFalsy(); + }); + }); + }); + describe('invalid equations', () => { + const validExpression = ['Math.round(A + B) / 100', '(A^2 - B) * 100']; + validExpression.forEach((exp) => { + it(exp, () => { + expect(exp.match(EQUATION_REGEX)).toBeTruthy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/components/validation.tsx b/x-pack/plugins/observability/public/pages/threshold/components/validation.tsx new file mode 100644 index 0000000000000..b9cfd302d28e7 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/components/validation.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fromKueryExpression } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; +import { isEmpty } from 'lodash'; +import { + Aggregators, + Comparator, + CustomMetricExpressionParams, + FilterQuery, + MetricExpressionParams, + QUERY_INVALID, +} from '../../../../common/threshold_rule/types'; + +export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; + +const isCustomMetricExpressionParams = ( + subject: MetricExpressionParams +): subject is CustomMetricExpressionParams => { + return subject.aggType === Aggregators.CUSTOM; +}; + +export function validateMetricThreshold({ + criteria, + filterQuery, +}: { + criteria: MetricExpressionParams[]; + filterQuery?: FilterQuery; +}): ValidationResult { + const validationResult = { errors: {} }; + const errors: { + [id: string]: { + aggField: string[]; + timeSizeUnit: string[]; + timeWindowSize: string[]; + critical: { + threshold0: string[]; + threshold1: string[]; + }; + warning: { + threshold0: string[]; + threshold1: string[]; + }; + metric: string[]; + customMetricsError?: string; + customMetrics: Record; + equation?: string; + }; + } & { filterQuery?: string[] } = {}; + validationResult.errors = errors; + + if (filterQuery === QUERY_INVALID) { + errors.filterQuery = [ + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery', { + defaultMessage: 'Filter query is invalid.', + }), + ]; + } + + if (!criteria || !criteria.length) { + return validationResult; + } + + criteria.forEach((c, idx) => { + // Create an id for each criteria, so we can map errors to specific criteria. + const id = idx.toString(); + + errors[id] = errors[id] || { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + critical: { + threshold0: [], + threshold1: [], + }, + warning: { + threshold0: [], + threshold1: [], + }, + metric: [], + filterQuery: [], + customMetrics: {}, + }; + if (!c.aggType) { + errors[id].aggField.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired', { + defaultMessage: 'Aggregation is required.', + }) + ); + } + + if (!c.threshold || !c.threshold.length) { + errors[id].critical.threshold0.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + + if (c.warningThreshold && !c.warningThreshold.length) { + errors[id].warning.threshold0.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + + for (const props of [ + { comparator: c.comparator, threshold: c.threshold, type: 'critical' }, + { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' }, + ]) { + // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. + // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. + const { comparator, threshold, type } = props as { + comparator?: Comparator; + threshold?: number[]; + type: 'critical' | 'warning'; + }; + if (threshold && threshold.length && ![...threshold].every(isNumber)) { + [...threshold].forEach((v, i) => { + if (!isNumber(v)) { + const key = i === 0 ? 'threshold0' : 'threshold1'; + errors[id][type][key].push( + i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired', + { + defaultMessage: 'Thresholds must contain a valid number.', + } + ) + ); + } + }); + } + + if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { + errors[id][type].threshold1.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { + defaultMessage: 'Threshold is required.', + }) + ); + } + } + + if (!c.timeSize) { + errors[id].timeWindowSize.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.timeRequred', { + defaultMessage: 'Time size is Required.', + }) + ); + } + + if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) { + errors[id].metric.push( + i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.metricRequired', { + defaultMessage: 'Metric is required.', + }) + ); + } + + if (isCustomMetricExpressionParams(c)) { + if (!c.customMetrics || (c.customMetrics && c.customMetrics.length < 1)) { + errors[id].customMetricsError = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.error.customMetricsError', + { + defaultMessage: 'You must define at least 1 custom metric', + } + ); + } else { + c.customMetrics.forEach((metric) => { + const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; + if (!metric.aggType) { + customMetricErrors.aggType = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.error.customMetrics.aggTypeRequired', + { + defaultMessage: 'Aggregation is required', + } + ); + } + if (metric.aggType !== 'count' && !metric.field) { + customMetricErrors.field = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.error.customMetrics.fieldRequired', + { + defaultMessage: 'Field is required', + } + ); + } + if (metric.aggType === 'count' && metric.filter) { + try { + fromKueryExpression(metric.filter); + } catch (e) { + customMetricErrors.filter = e.message; + } + } + if (!isEmpty(customMetricErrors)) { + errors[id].customMetrics[metric.name] = customMetricErrors; + } + }); + } + + if (c.equation && c.equation.match(EQUATION_REGEX)) { + errors[id].equation = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters', + { + defaultMessage: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + ); + } + } + }); + + return validationResult; +} +const isNumber = (value: unknown): value is number => typeof value === 'number'; diff --git a/x-pack/plugins/observability/public/pages/threshold/containers/with_kuery_autocompletion.tsx b/x-pack/plugins/observability/public/pages/threshold/containers/with_kuery_autocompletion.tsx new file mode 100644 index 0000000000000..4426952cf173e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/containers/with_kuery_autocompletion.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { DataViewBase } from '@kbn/es-query'; +import { + withKibana, + KibanaReactContextValue, + KibanaServices, +} from '@kbn/kibana-react-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import { InfraClientStartDeps, RendererFunction } from '../types'; + +interface WithKueryAutocompletionLifecycleProps { + kibana: KibanaReactContextValue; + children: RendererFunction<{ + isLoadingSuggestions: boolean; + loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; + suggestions: QuerySuggestion[]; + }>; + indexPattern: DataViewBase; +} + +interface WithKueryAutocompletionLifecycleState { + // lacking cancellation support in the autocompletion api, + // this is used to keep older, slower requests from clobbering newer ones + currentRequest: { + expression: string; + cursorPosition: number; + } | null; + suggestions: QuerySuggestion[]; +} + +class WithKueryAutocompletionComponent extends React.Component< + WithKueryAutocompletionLifecycleProps, + WithKueryAutocompletionLifecycleState +> { + public readonly state: WithKueryAutocompletionLifecycleState = { + currentRequest: null, + suggestions: [], + }; + + public render() { + const { currentRequest, suggestions } = this.state; + + return this.props.children({ + isLoadingSuggestions: currentRequest !== null, + loadSuggestions: this.loadSuggestions, + suggestions, + }); + } + + private loadSuggestions = async ( + expression: string, + cursorPosition: number, + maxSuggestions?: number, + transformSuggestions?: (s: QuerySuggestion[]) => QuerySuggestion[] + ) => { + const { indexPattern } = this.props; + const language = 'kuery'; + const hasQuerySuggestions = + this.props.kibana.services.unifiedSearch.autocomplete.hasQuerySuggestions(language); + + if (!hasQuerySuggestions) { + return; + } + + this.setState({ + currentRequest: { + expression, + cursorPosition, + }, + suggestions: [], + }); + + const suggestions = + (await this.props.kibana.services.unifiedSearch.autocomplete.getQuerySuggestions({ + language, + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + indexPatterns: [indexPattern as DataView], + boolFilter: [], + })) || []; + + const transformedSuggestions = transformSuggestions + ? transformSuggestions(suggestions) + : suggestions; + + this.setState((state) => + state.currentRequest && + state.currentRequest.expression !== expression && + state.currentRequest.cursorPosition !== cursorPosition + ? state // ignore this result, since a newer request is in flight + : { + ...state, + currentRequest: null, + suggestions: maxSuggestions + ? transformedSuggestions.slice(0, maxSuggestions) + : transformedSuggestions, + } + ); + }; +} + +export const WithKueryAutocompletion = withKibana( + WithKueryAutocompletionComponent +); diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/calculate_domain.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/calculate_domain.ts new file mode 100644 index 0000000000000..17a3b3eee7726 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/calculate_domain.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 { min, max, sum, isNumber } from 'lodash'; +import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; +import { getMetricId } from './get_metric_id'; + +const getMin = (values: Array) => { + const minValue = min(values); + return isNumber(minValue) && Number.isFinite(minValue) ? minValue : undefined; +}; + +const getMax = (values: Array) => { + const maxValue = max(values); + return isNumber(maxValue) && Number.isFinite(maxValue) ? maxValue : undefined; +}; + +export const calculateDomain = ( + series: MetricsExplorerSeries, + metrics: MetricsExplorerOptionsMetric[], + stacked = false +): { min: number; max: number } => { + const values = series.rows + .reduce((acc, row) => { + const rowValues = metrics + .map((m, index) => { + return (row[getMetricId(m, index)] as number) || null; + }) + .filter((v) => isNumber(v)); + const minValue = getMin(rowValues); + // For stacked domains we want to add 10% head room so the charts have + // enough room to draw the 2 pixel line as well. + const maxValue = stacked ? sum(rowValues) * 1.1 : getMax(rowValues); + return acc.concat([minValue || null, maxValue || null]); + }, [] as Array) + .filter((v) => isNumber(v)); + return { min: getMin(values) || 0, max: getMax(values) || 0 }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.test.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.test.ts new file mode 100644 index 0000000000000..018ef6fa9681c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.test.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 { decimalToPct, pctToDecimal } from './corrected_percent_convert'; + +describe('decimalToPct', () => { + test('should retain correct floating point precision up to 10 decimal places', () => { + // Most of these cases would still work fine just doing x * 100 instead of passing it through + // decimalToPct, but the function still needs to work regardless + expect(decimalToPct(0)).toBe(0); + expect(decimalToPct(0.1)).toBe(10); + expect(decimalToPct(0.01)).toBe(1); + expect(decimalToPct(0.014)).toBe(1.4); + expect(decimalToPct(0.0141)).toBe(1.41); + expect(decimalToPct(0.01414)).toBe(1.414); + // This case is known to fail without decimalToPct; vanilla JS 0.014141 * 100 === 1.4141000000000001 + expect(decimalToPct(0.014141)).toBe(1.4141); + expect(decimalToPct(0.0141414)).toBe(1.41414); + expect(decimalToPct(0.01414141)).toBe(1.414141); + expect(decimalToPct(0.014141414)).toBe(1.4141414); + }); + test('should also work with values greater than 1', () => { + expect(decimalToPct(2)).toBe(200); + expect(decimalToPct(2.1)).toBe(210); + expect(decimalToPct(2.14)).toBe(214); + expect(decimalToPct(2.14141414)).toBe(214.141414); + }); +}); + +describe('pctToDecimal', () => { + test('should retain correct floating point precision up to 10 decimal places', () => { + expect(pctToDecimal(0)).toBe(0); + expect(pctToDecimal(10)).toBe(0.1); + expect(pctToDecimal(1)).toBe(0.01); + expect(pctToDecimal(1.4)).toBe(0.014); + expect(pctToDecimal(1.41)).toBe(0.0141); + expect(pctToDecimal(1.414)).toBe(0.01414); + expect(pctToDecimal(1.4141)).toBe(0.014141); + expect(pctToDecimal(1.41414)).toBe(0.0141414); + expect(pctToDecimal(1.414141)).toBe(0.01414141); + expect(pctToDecimal(1.4141414)).toBe(0.014141414); + }); + test('should also work with values greater than 100%', () => { + expect(pctToDecimal(200)).toBe(2); + expect(pctToDecimal(210)).toBe(2.1); + expect(pctToDecimal(214)).toBe(2.14); + expect(pctToDecimal(214.141414)).toBe(2.14141414); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.ts new file mode 100644 index 0000000000000..4df4417890abc --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/corrected_percent_convert.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const correctedPctConvert = (v: number, decimalToPct: boolean) => { + // Correct floating point precision + const replacementPattern = decimalToPct ? new RegExp(/0?\./) : '.'; + const numberOfDigits = String(v).replace(replacementPattern, '').length; + const multipliedValue = decimalToPct ? v * 100 : v / 100; + return parseFloat(multipliedValue.toPrecision(numberOfDigits)); +}; + +export const decimalToPct = (v: number) => correctedPctConvert(v, true); +export const pctToDecimal = (v: number) => correctedPctConvert(v, false); diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metric.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metric.ts new file mode 100644 index 0000000000000..7cb96af683573 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metric.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 numeral from '@elastic/numeral'; +import { InfraFormatterType } from '../../../../common/threshold_rule/types'; +import { createFormatter } from '../../../../common/threshold_rule/formatters'; +import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { metricToFormat } from './metric_to_format'; + +export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => { + if (metric?.aggregation === 'custom') { + return (input: number) => numeral(input).format('0.[0000]'); + } + if (metric && metric.field) { + const format = metricToFormat(metric); + if (format === InfraFormatterType.bits && metric.aggregation === 'rate') { + return createFormatter(InfraFormatterType.bits, '{{value}}/s'); + } + return createFormatter(format); + } + return createFormatter(InfraFormatterType.number); +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metrics.test.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metrics.test.ts new file mode 100644 index 0000000000000..07ed09095a6aa --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/create_formatter_for_metrics.test.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 { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { createFormatterForMetric } from './create_formatter_for_metric'; + +describe('createFormatterForMetric()', () => { + it('should just work for count', () => { + const metric: MetricsExplorerMetric = { aggregation: 'count' }; + const format = createFormatterForMetric(metric); + expect(format(1291929)).toBe('1,291,929'); + }); + it('should just work for numerics', () => { + const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.load.1' }; + const format = createFormatterForMetric(metric); + expect(format(1000.2)).toBe('1,000.2'); + }); + it('should just work for percents', () => { + const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.cpu.total.pct' }; + const format = createFormatterForMetric(metric); + expect(format(0.349)).toBe('34.9%'); + }); + it('should just work for rates', () => { + const metric: MetricsExplorerMetric = { + aggregation: 'rate', + field: 'host.network.egress.bytes', + }; + const format = createFormatterForMetric(metric); + expect(format(103929292)).toBe('831.4 Mbit/s'); + }); + it('should just work for bytes', () => { + const metric: MetricsExplorerMetric = { + aggregation: 'avg', + field: 'host.network.egress.bytes', + }; + const format = createFormatterForMetric(metric); + expect(format(103929292)).toBe('103.9 MB'); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.test.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.test.ts new file mode 100644 index 0000000000000..f1fe34c377f13 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { createMetricLabel } from './create_metric_label'; + +describe('createMetricLabel()', () => { + it('should work with metrics with fields', () => { + const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.load.1' }; + expect(createMetricLabel(metric)).toBe('avg(system.load.1)'); + }); + it('should work with document count', () => { + const metric: MetricsExplorerMetric = { aggregation: 'count' }; + expect(createMetricLabel(metric)).toBe('count()'); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.ts new file mode 100644 index 0000000000000..1b3496c07cd43 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/create_metric_label.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 { MetricsExplorerOptionsMetric } from '../hooks/use_metrics_explorer_options'; + +export const createMetricLabel = (metric: MetricsExplorerOptionsMetric) => { + if (metric.label) { + return metric.label; + } + return `${metric.aggregation}(${metric.field || ''})`; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/get_metric_id.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/get_metric_id.ts new file mode 100644 index 0000000000000..969ade79e4dda --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/get_metric_id.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. + */ + +import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; + +export const getMetricId = (metric: MetricsExplorerOptionsMetric, index: string | number) => { + return `metric_${index}`; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/kuery.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/kuery.ts new file mode 100644 index 0000000000000..aec9ec58aabaa --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/kuery.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 { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { DataViewBase } from '@kbn/es-query'; + +export const convertKueryToElasticSearchQuery = ( + kueryExpression: string, + indexPattern: DataViewBase, + swallowErrors: boolean = true +) => { + try { + return kueryExpression + ? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern)) + : ''; + } catch (err) { + if (swallowErrors) { + return ''; + } else throw err; + } +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/metric_to_format.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/metric_to_format.ts new file mode 100644 index 0000000000000..0ed004793b296 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/metric_to_format.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 { last } from 'lodash'; +import { InfraFormatterType } from '../../../../common/threshold_rule/types'; +import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; + +export const metricToFormat = (metric?: MetricsExplorerMetric) => { + if (metric && metric.field) { + const suffix = last(metric.field.split(/\./)); + if (suffix === 'pct') { + return InfraFormatterType.percent; + } + if (suffix === 'bytes' && metric.aggregation === 'rate') { + return InfraFormatterType.bits; + } + if (suffix === 'bytes') { + return InfraFormatterType.bytes; + } + } + return InfraFormatterType.number; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/notifications.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/notifications.ts new file mode 100644 index 0000000000000..914333439f426 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/notifications.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +export const useSourceNotifier = () => { + const { notifications } = useKibana(); + + const updateFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: i18n.translate( + 'xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle', + { + defaultMessage: 'Configuration update failed', + } + ), + body: [ + i18n.translate('xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody', { + defaultMessage: + "We couldn't apply the changes to the Metrics configuration. Try again later.", + }), + message, + ] + .filter(Boolean) + .join(' '), + }); + }; + + const updateSuccess = () => { + notifications.toasts.success({ + toastLifeTimeMs: 3000, + title: i18n.translate( + 'xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle', + { + defaultMessage: 'Metrics settings successfully updated', + } + ), + }); + }; + + return { + updateFailure, + updateSuccess, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/runtime_types.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/runtime_types.ts new file mode 100644 index 0000000000000..18156e8792d7d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/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 { 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'; +import type { RouteValidationFunction } from '@kbn/core/server'; + +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/observability/public/pages/threshold/helpers/source.tsx b/x-pack/plugins/observability/public/pages/threshold/helpers/source.tsx new file mode 100644 index 0000000000000..bf1ad0134ea8f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/source.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import createContainer from 'constate'; +import React, { useEffect, useState } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { IHttpFetchError } from '@kbn/core-http-browser'; +import { + MetricsSourceConfiguration, + MetricsSourceConfigurationResponse, + PartialMetricsSourceConfigurationProperties, +} from '../../../../common/threshold_rule/types'; +import { MissingHttpClientException } from './source_errors'; +import { useTrackedPromise } from '../hooks/use_tracked_promise'; +import { useSourceNotifier } from './notifications'; + +export const pickIndexPattern = ( + source: MetricsSourceConfiguration | undefined, + type: 'metrics' +) => { + if (!source) { + return 'unknown-index'; + } + if (type === 'metrics') { + return source.configuration.metricAlias; + } + return `${source.configuration.metricAlias}`; +}; + +export const useSource = ({ sourceId }: { sourceId: string }) => { + const { services } = useKibana(); + + const notify = useSourceNotifier(); + + const fetchService = services.http; + const API_URL = `/api/metrics/source/${sourceId}`; + + const [source, setSource] = useState(undefined); + + const [loadSourceRequest, loadSource] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: () => { + if (!fetchService) { + throw new MissingHttpClientException(); + } + + return fetchService.fetch(API_URL, { method: 'GET' }); + }, + onResolve: (response) => { + if (response) { + setSource(response.source); + } + }, + }, + [fetchService, sourceId] + ); + + const [createSourceConfigurationRequest, createSourceConfiguration] = useTrackedPromise( + { + createPromise: async (sourceProperties: PartialMetricsSourceConfigurationProperties) => { + if (!fetchService) { + throw new MissingHttpClientException(); + } + + return await fetchService.patch(API_URL, { + method: 'PATCH', + body: JSON.stringify(sourceProperties), + }); + }, + onResolve: (response) => { + if (response) { + notify.updateSuccess(); + setSource(response.source); + } + }, + onReject: (error) => { + notify.updateFailure((error as IHttpFetchError<{ message: string }>).body?.message); + }, + }, + [fetchService, sourceId] + ); + + useEffect(() => { + loadSource(); + }, [loadSource, sourceId]); + + const createDerivedIndexPattern = () => { + return { + fields: source?.status ? source.status.indexFields : [], + title: pickIndexPattern(source, 'metrics'), + }; + }; + + const hasFailedLoadingSource = loadSourceRequest.state === 'rejected'; + const isUninitialized = loadSourceRequest.state === 'uninitialized'; + const isLoadingSource = loadSourceRequest.state === 'pending'; + const isLoading = isLoadingSource || createSourceConfigurationRequest.state === 'pending'; + + const sourceExists = source ? !!source.version : undefined; + + const metricIndicesExist = Boolean(source?.status?.metricIndicesExist); + + const version = source?.version; + + return { + createSourceConfiguration, + createDerivedIndexPattern, + isLoading, + isLoadingSource, + isUninitialized, + hasFailedLoadingSource, + loadSource, + loadSourceRequest, + loadSourceFailureMessage: hasFailedLoadingSource ? `${loadSourceRequest.value}` : undefined, + metricIndicesExist, + source, + sourceExists, + sourceId, + updateSourceConfiguration: createSourceConfiguration, + version, + }; +}; + +export const [SourceProvider, useSourceContext] = createContainer(useSource); + +export const withSourceProvider = + (Component: React.FunctionComponent) => + (sourceId = 'default') => { + // eslint-disable-next-line react/function-component-definition + return function ComponentWithSourceProvider(props: ComponentProps) { + return ( + + + + ); + }; + }; diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/source_errors.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/source_errors.ts new file mode 100644 index 0000000000000..45cbbad7d3e3a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/source_errors.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +const missingHttpMessage = i18n.translate( + 'xpack.observability.threshold.rule.sourceConfiguration.missingHttp', + { + defaultMessage: 'Failed to load source: No HTTP client available.', + } +); + +/** + * Errors + */ +export class MissingHttpClientException extends Error { + constructor() { + super(missingHttpMessage); + Object.setPrototypeOf(this, new.target.prototype); + this.name = 'MissingHttpClientException'; + } +} diff --git a/x-pack/plugins/observability/public/pages/threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/pages/threshold/helpers/use_alert_prefill.ts new file mode 100644 index 0000000000000..3f659b1041762 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/helpers/use_alert_prefill.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 createContainer from 'constate'; +import { useMetricThresholdAlertPrefill } from '../hooks/use_metric_threshold_alert_prefill'; + +const useAlertPrefill = () => { + const metricThresholdPrefill = useMetricThresholdAlertPrefill(); + return { metricThresholdPrefill }; +}; + +export const [AlertPrefillProvider, useAlertPrefillContext] = createContainer(useAlertPrefill); diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_time_zone_setting.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_time_zone_setting.ts new file mode 100644 index 0000000000000..6ea4ecee25ca2 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_time_zone_setting.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment-timezone'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; + +export function useKibanaTimeZoneSetting() { + const [kibanaTimeZone] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); + + if (!kibanaTimeZone || kibanaTimeZone === 'Browser') { + return moment.tz.guess(); + } + + return kibanaTimeZone; +} diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_timefilter_time.tsx b/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_timefilter_time.tsx new file mode 100644 index 0000000000000..ca94d4a33462a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_kibana_timefilter_time.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, useEffect } from 'react'; + +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; +import useMount from 'react-use/lib/useMount'; +import type { TimeRange } from '@kbn/es-query'; +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { useKibana } from '../../../utils/kibana_react'; + +export const useKibanaTimefilterTime = ({ + from: fromDefault, + to: toDefault, +}: TimeRange): [typeof getTime, TimefilterContract['setTime']] => { + const { services } = useKibana(); + + const getTime = useCallback(() => { + const timefilterService = services.data.query.timefilter.timefilter; + return timefilterService.isTimeTouched() + ? timefilterService.getTime() + : { from: fromDefault, to: toDefault }; + }, [services.data.query.timefilter.timefilter, fromDefault, toDefault]); + + return [getTime, services.data.query.timefilter.timefilter.setTime]; +}; + +/** + * Handles one or two way syncing with the Kibana time filter service. + * + * For one way syncing the time range will be synced back to the time filter service + * on mount *if* it differs from the defaults, e.g. a URL param. + * Future updates, after mount, will also be synced back to the time filter service. + * + * For two way syncing, in addition to the above, changes *from* the time filter service + * will be sycned to the solution, e.g. there might be an embeddable on the page that + * fires an action that hooks into the time filter service. + */ +export const useSyncKibanaTimeFilterTime = ( + defaults: TimeRange, + currentTimeRange: TimeRange, + setTimeRange?: (timeRange: TimeRange) => void +) => { + const { services } = useKibana(); + const [getTime, setTime] = useKibanaTimefilterTime(defaults); + + // On first mount we only want to sync time with Kibana if the derived currentTimeRange (e.g. from URL params) + // differs from our defaults. + useMount(() => { + if (defaults.from !== currentTimeRange.from || defaults.to !== currentTimeRange.to) { + setTime({ from: currentTimeRange.from, to: currentTimeRange.to }); + } + }); + + // Sync explicit changes *after* mount from the solution back to Kibana + useUpdateEffect(() => { + setTime({ from: currentTimeRange.from, to: currentTimeRange.to }); + }, [currentTimeRange.from, currentTimeRange.to, setTime]); + + // *Optionally* sync time filter service changes back to the solution. + // For example, an embeddable might have a time range action that hooks into + // the time filter service. + useEffect(() => { + const sub = services.data.query.timefilter.timefilter.getTimeUpdate$().subscribe(() => { + if (setTimeRange) { + const timeRange = getTime(); + setTimeRange(timeRange); + } + }); + + return () => sub.unsubscribe(); + }, [getTime, setTimeRange, services.data.query.timefilter.timefilter]); +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metric_threshold_alert_prefill.ts new file mode 100644 index 0000000000000..86262d0f272c2 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metric_threshold_alert_prefill.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 { isEqual } from 'lodash'; +import { useState } from 'react'; +import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; + +export interface MetricThresholdPrefillOptions { + groupBy: string | string[] | undefined; + filterQuery: string | undefined; + metrics: MetricsExplorerMetric[]; +} + +export const useMetricThresholdAlertPrefill = () => { + const [prefillOptionsState, setPrefillOptionsState] = useState({ + groupBy: undefined, + filterQuery: undefined, + metrics: [], + }); + + const { groupBy, filterQuery, metrics } = prefillOptionsState; + + return { + groupBy, + filterQuery, + metrics, + setPrefillOptions(newState: MetricThresholdPrefillOptions) { + if (!isEqual(newState, prefillOptionsState)) setPrefillOptionsState(newState); + }, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_chart_data.ts new file mode 100644 index 0000000000000..1f70d4f84808f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_chart_data.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import DateMath from '@kbn/datemath'; +import { DataViewBase } from '@kbn/es-query'; +import { useMemo } from 'react'; +import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer'; +import { + MetricExpressionCustomMetric, + MetricsSourceConfiguration, +} from '../../../../common/threshold_rule/types'; +import { MetricExpression, TimeRange } from '../types'; +import { useMetricsExplorerData } from './use_metrics_explorer_data'; + +import { + MetricsExplorerOptions, + MetricsExplorerTimestampsRT, +} from './use_metrics_explorer_options'; + +const DEFAULT_TIME_RANGE = {}; + +export const useMetricsExplorerChartData = ( + expression: MetricExpression, + derivedIndexPattern: DataViewBase, + source?: MetricsSourceConfiguration, + filterQuery?: string, + groupBy?: string | string[], + timeRange: TimeRange = DEFAULT_TIME_RANGE +) => { + const { timeSize, timeUnit } = expression || { timeSize: 1, timeUnit: 'm' }; + + const options: MetricsExplorerOptions = useMemo( + () => ({ + limit: 1, + forceInterval: true, + dropLastBucket: false, + groupBy, + filterQuery, + metrics: [ + expression.aggType === 'custom' + ? { + aggregation: 'custom', + custom_metrics: + expression?.customMetrics?.map(mapMetricThresholdMetricToMetricsExplorerMetric) ?? + [], + equation: expression.equation, + } + : { field: expression.metric, aggregation: expression.aggType }, + ], + aggregation: expression.aggType || 'avg', + }), + [ + expression.aggType, + expression.equation, + expression.metric, + expression.customMetrics, + filterQuery, + groupBy, + ] + ); + const timestamps: MetricsExplorerTimestampsRT = useMemo(() => { + const from = timeRange.from ?? `now-${(timeSize || 1) * 20}${timeUnit}`; + const to = timeRange.to ?? 'now'; + const fromTimestamp = DateMath.parse(from)!.valueOf(); + const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf(); + return { + interval: `>=${timeSize || 1}${timeUnit}`, + fromTimestamp, + toTimestamp, + }; + }, [timeRange, timeSize, timeUnit]); + + return useMetricsExplorerData(options, source?.configuration, derivedIndexPattern, timestamps); +}; + +const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => { + if (metric.aggType === 'count') { + return { + name: metric.name, + aggregation: 'count' as MetricExplorerCustomMetricAggregations, + filter: metric.filter, + }; + } + + return { + name: metric.name, + aggregation: metric.aggType as MetricExplorerCustomMetricAggregations, + field: metric.field, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.test.tsx new file mode 100644 index 0000000000000..d2c8955e00913 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.test.tsx @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useMetricsExplorerData } from './use_metrics_explorer_data'; + +import { renderHook } from '@testing-library/react-hooks'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; + +import { + MetricsExplorerOptions, + MetricsExplorerTimestampsRT, +} from './use_metrics_explorer_options'; +import { DataViewBase } from '@kbn/es-query'; +import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types'; +import { + createSeries, + derivedIndexPattern, + options, + resp, + source, + timestamps, +} from '../../../utils/metrics_explorer'; + +const mockedFetch = jest.fn(); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, + }, + }, +}); + +const renderUseMetricsExplorerDataHook = () => { + const wrapper: React.FC = ({ children }) => { + const services = { + http: { + post: mockedFetch, + }, + }; + return ( + + {children} + + ); + }; + return renderHook( + (props: { + options: MetricsExplorerOptions; + source: MetricsSourceConfigurationProperties | undefined; + derivedIndexPattern: DataViewBase; + timestamps: MetricsExplorerTimestampsRT; + }) => + useMetricsExplorerData( + props.options, + props.source, + props.derivedIndexPattern, + props.timestamps + ), + { + initialProps: { + options, + source, + derivedIndexPattern, + timestamps, + }, + wrapper, + } + ); +}; + +jest.mock('../helpers/kuery', () => { + return { + convertKueryToElasticSearchQuery: (query: string) => query, + }; +}); + +describe('useMetricsExplorerData Hook', () => { + afterEach(() => { + queryClient.clear(); + }); + + it('should just work', async () => { + mockedFetch.mockResolvedValue(resp); + const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + + expect(result.current.data).toBeUndefined(); + expect(result.current.isLoading).toBe(true); + + await waitForNextUpdate(); + + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + const { series } = result.current.data!.pages[0]; + expect(series).toBeDefined(); + expect(series.length).toBe(3); + }); + + it('should paginate', async () => { + mockedFetch.mockResolvedValue(resp); + const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + expect(result.current.data).toBeUndefined(); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + const { series } = result.current.data!.pages[0]; + expect(series).toBeDefined(); + expect(series.length).toBe(3); + mockedFetch.mockResolvedValue({ + pageInfo: { total: 10, afterKey: 'host-06' }, + series: [createSeries('host-04'), createSeries('host-05'), createSeries('host-06')], + } as any); + result.current.fetchNextPage(); + await waitForNextUpdate(); + expect(result.current.isLoading).toBe(false); + const { series: nextSeries } = result.current.data!.pages[1]; + expect(nextSeries).toBeDefined(); + expect(nextSeries.length).toBe(3); + }); + + it('should reset error upon recovery', async () => { + const error = new Error('Network Error'); + mockedFetch.mockRejectedValue(error); + const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + expect(result.current.data).toBeUndefined(); + expect(result.current.error).toEqual(null); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data).toBeUndefined(); + expect(result.current.error).toEqual(error); + expect(result.current.isLoading).toBe(false); + mockedFetch.mockResolvedValue(resp as any); + result.current.refetch(); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBe(null); + }); + + it('should not paginate on option change', async () => { + mockedFetch.mockResolvedValue(resp as any); + const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook(); + expect(result.current.data).toBeUndefined(); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + const { series } = result.current.data!.pages[0]; + expect(series).toBeDefined(); + expect(series.length).toBe(3); + mockedFetch.mockResolvedValue(resp as any); + rerender({ + options: { + ...options, + aggregation: 'count', + metrics: [{ aggregation: 'count' }], + }, + source, + derivedIndexPattern, + timestamps, + }); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + }); + + it('should not paginate on time change', async () => { + mockedFetch.mockResolvedValue(resp as any); + const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook(); + expect(result.current.data).toBeUndefined(); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + const { series } = result.current.data!.pages[0]; + expect(series).toBeDefined(); + expect(series.length).toBe(3); + mockedFetch.mockResolvedValue(resp as any); + rerender({ + options, + source, + derivedIndexPattern, + timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' }, + }); + expect(result.current.isLoading).toBe(true); + await waitForNextUpdate(); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.isLoading).toBe(false); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.ts new file mode 100644 index 0000000000000..206e8fe3818f1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_data.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewBase } from '@kbn/es-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types'; +import { + MetricsExplorerResponse, + metricsExplorerResponseRT, +} from '../../../../common/threshold_rule/metrics_explorer'; + +import { + MetricsExplorerOptions, + MetricsExplorerTimestampsRT, +} from './use_metrics_explorer_options'; +import { convertKueryToElasticSearchQuery } from '../helpers/kuery'; +import { decodeOrThrow } from '../helpers/runtime_types'; + +export function useMetricsExplorerData( + options: MetricsExplorerOptions, + source: MetricsSourceConfigurationProperties | undefined, + derivedIndexPattern: DataViewBase, + { fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT, + enabled = true +) { + const { http } = useKibana().services; + + const { isLoading, data, error, refetch, fetchNextPage } = useInfiniteQuery< + MetricsExplorerResponse, + Error + >({ + queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp], + queryFn: async ({ signal, pageParam = { afterKey: null } }) => { + if (!fromTimestamp || !toTimestamp) { + throw new Error('Unable to parse timerange'); + } + if (!http) { + throw new Error('HTTP service is unavailable'); + } + if (!source) { + throw new Error('Source is unavailable'); + } + + const { afterKey } = pageParam; + const response = await http.post('/api/infra/metrics_explorer', { + method: 'POST', + body: JSON.stringify({ + forceInterval: options.forceInterval, + dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true, + metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] : options.metrics, + groupBy: options.groupBy, + afterKey, + limit: options.limit, + indexPattern: source.metricAlias, + filterQuery: + (options.filterQuery && + convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) || + void 0, + timerange: { + interval, + from: fromTimestamp, + to: toTimestamp, + }, + }), + signal, + }); + + return decodeOrThrow(metricsExplorerResponseRT)(response); + }, + getNextPageParam: (lastPage) => lastPage.pageInfo, + enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!source, + refetchOnWindowFocus: false, + }); + + return { + data, + error, + fetchNextPage, + isLoading, + refetch, + }; +} diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.test.tsx new file mode 100644 index 0000000000000..9def4a44d6a8e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.test.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { + useMetricsExplorerOptions, + MetricsExplorerOptions, + MetricsExplorerTimeOptions, + DEFAULT_OPTIONS, + DEFAULT_TIMERANGE, +} from './use_metrics_explorer_options'; + +let PREFILL: Record = {}; +jest.mock('../helpers/use_alert_prefill', () => ({ + useAlertPrefillContext: () => ({ + metricThresholdPrefill: { + setPrefillOptions(opts: Record) { + PREFILL = opts; + }, + }, + }), +})); + +jest.mock('./use_kibana_timefilter_time', () => ({ + useKibanaTimefilterTime: (defaults: { from: string; to: string }) => [() => defaults], + useSyncKibanaTimeFilterTime: () => [() => {}], +})); + +const renderUseMetricsExplorerOptionsHook = () => renderHook(() => useMetricsExplorerOptions()); + +interface LocalStore { + [key: string]: string; +} + +interface LocalStorage { + getItem: (key: string) => string | null; + setItem: (key: string, value: string) => void; +} + +const STORE: LocalStore = {}; +const localStorageMock: LocalStorage = { + getItem: (key: string) => { + return STORE[key] || null; + }, + setItem: (key: string, value: any) => { + STORE[key] = value.toString(); + }, +}; + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}); + +describe('useMetricExplorerOptions', () => { + beforeEach(() => { + delete STORE.MetricsExplorerOptions; + delete STORE.MetricsExplorerTimeRange; + PREFILL = {}; + }); + + it('should just work', () => { + const { result } = renderUseMetricsExplorerOptionsHook(); + expect(result.current.options).toEqual(DEFAULT_OPTIONS); + expect(result.current.timeRange).toEqual(DEFAULT_TIMERANGE); + expect(result.current.isAutoReloading).toEqual(false); + expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(DEFAULT_OPTIONS)); + }); + + it('should change the store when options update', () => { + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); + const newOptions: MetricsExplorerOptions = { + ...DEFAULT_OPTIONS, + metrics: [{ aggregation: 'count' }], + }; + act(() => { + result.current.setOptions(newOptions); + }); + rerender(); + expect(result.current.options).toEqual(newOptions); + expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(newOptions)); + }); + + it('should change the store when timerange update', () => { + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); + const newTimeRange: MetricsExplorerTimeOptions = { + ...DEFAULT_TIMERANGE, + from: 'now-15m', + }; + act(() => { + result.current.setTimeRange(newTimeRange); + }); + rerender(); + expect(result.current.timeRange).toEqual(newTimeRange); + }); + + it('should load from store when available', () => { + const newOptions: MetricsExplorerOptions = { + ...DEFAULT_OPTIONS, + metrics: [{ aggregation: 'avg', field: 'system.load.1' }], + }; + STORE.MetricsExplorerOptions = JSON.stringify(newOptions); + const { result } = renderUseMetricsExplorerOptionsHook(); + expect(result.current.options).toEqual(newOptions); + }); + + it('should sync the options to the threshold alert preview context', () => { + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); + + const newOptions: MetricsExplorerOptions = { + ...DEFAULT_OPTIONS, + metrics: [{ aggregation: 'count' }], + filterQuery: 'foo', + groupBy: 'host.hostname', + }; + act(() => { + result.current.setOptions(newOptions); + }); + rerender(); + expect(PREFILL.metrics).toEqual(newOptions.metrics); + expect(PREFILL.groupBy).toEqual(newOptions.groupBy); + expect(PREFILL.filterQuery).toEqual(newOptions.filterQuery); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.ts new file mode 100644 index 0000000000000..6309bddd29539 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/use_metrics_explorer_options.ts @@ -0,0 +1,234 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 from '@kbn/datemath'; +import * as t from 'io-ts'; +import { values } from 'lodash'; +import createContainer from 'constate'; +import type { TimeRange } from '@kbn/es-query'; +import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; + +import { metricsExplorerMetricRT } from '../../../../common/threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/threshold_rule/color_palette'; + +import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; +import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime } from './use_kibana_timefilter_time'; + +const metricsExplorerOptionsMetricRT = t.intersection([ + metricsExplorerMetricRT, + t.partial({ + rate: t.boolean, + color: t.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), + label: t.string, + }), +]); + +export type MetricsExplorerOptionsMetric = t.TypeOf; + +export enum MetricsExplorerChartType { + line = 'line', + area = 'area', + bar = 'bar', +} + +export enum MetricsExplorerYAxisMode { + fromZero = 'fromZero', + auto = 'auto', +} + +export const metricsExplorerChartOptionsRT = t.type({ + yAxisMode: t.keyof( + Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< + MetricsExplorerYAxisMode, + null + > + ), + type: t.keyof( + Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record< + MetricsExplorerChartType, + null + > + ), + stack: t.boolean, +}); + +export type MetricsExplorerChartOptions = t.TypeOf; + +const metricExplorerOptionsRequiredRT = t.type({ + aggregation: t.string, + metrics: t.array(metricsExplorerOptionsMetricRT), +}); + +const metricExplorerOptionsOptionalRT = t.partial({ + limit: t.number, + groupBy: t.union([t.string, t.array(t.string)]), + filterQuery: t.string, + source: t.string, + forceInterval: t.boolean, + dropLastBucket: t.boolean, +}); +export const metricExplorerOptionsRT = t.intersection([ + metricExplorerOptionsRequiredRT, + metricExplorerOptionsOptionalRT, +]); + +export type MetricsExplorerOptions = t.TypeOf; + +export const metricsExplorerTimestampsRT = t.type({ + fromTimestamp: t.number, + toTimestamp: t.number, + interval: t.string, +}); +export type MetricsExplorerTimestampsRT = t.TypeOf; + +export const metricsExplorerTimeOptionsRT = t.type({ + from: t.string, + to: t.string, + interval: t.string, +}); +export type MetricsExplorerTimeOptions = t.TypeOf; + +export const DEFAULT_TIMERANGE: MetricsExplorerTimeOptions = { + from: 'now-1h', + to: 'now', + interval: '>=10s', +}; + +export const DEFAULT_CHART_OPTIONS: MetricsExplorerChartOptions = { + type: MetricsExplorerChartType.line, + yAxisMode: MetricsExplorerYAxisMode.fromZero, + stack: false, +}; + +export const DEFAULT_METRICS: MetricsExplorerOptionsMetric[] = [ + { + aggregation: 'avg', + field: 'system.cpu.total.norm.pct', + color: Color.color0, + }, + { + aggregation: 'avg', + field: 'kubernetes.pod.cpu.usage.node.pct', + color: Color.color1, + }, + { + aggregation: 'avg', + field: 'docker.cpu.total.pct', + color: Color.color2, + }, +]; + +export const DEFAULT_OPTIONS: MetricsExplorerOptions = { + aggregation: 'avg', + metrics: DEFAULT_METRICS, + source: 'default', +}; + +export const DEFAULT_METRICS_EXPLORER_VIEW_STATE = { + options: DEFAULT_OPTIONS, + chartOptions: DEFAULT_CHART_OPTIONS, + currentTimerange: DEFAULT_TIMERANGE, +}; + +function parseJsonOrDefault(value: string | null, defaultValue: Obj): Obj { + if (!value) { + return defaultValue; + } + try { + return JSON.parse(value) as Obj; + } catch (e) { + return defaultValue; + } +} + +function useStateWithLocalStorage( + key: string, + defaultState: State +): [State, Dispatch>] { + const storageState = localStorage.getItem(key); + const [state, setState] = useState(parseJsonOrDefault(storageState, defaultState)); + useEffect(() => { + localStorage.setItem(key, JSON.stringify(state)); + }, [key, state]); + return [state, setState]; +} + +const getDefaultTimeRange = ({ from, to }: TimeRange) => { + const fromTimestamp = DateMath.parse(from)!.valueOf(); + const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf(); + return { + fromTimestamp, + toTimestamp, + interval: DEFAULT_TIMERANGE.interval, + }; +}; + +export const useMetricsExplorerOptions = () => { + const TIME_DEFAULTS = { from: 'now-1h', to: 'now' }; + const [getTime] = useKibanaTimefilterTime(TIME_DEFAULTS); + const { from, to } = getTime(); + + const [options, setOptions] = useStateWithLocalStorage( + 'MetricsExplorerOptions', + DEFAULT_OPTIONS + ); + const [timeRange, setTimeRange] = useState({ + from, + to, + interval: DEFAULT_TIMERANGE.interval, + }); + const [timestamps, setTimestamps] = useState( + getDefaultTimeRange({ from, to }) + ); + + useSyncKibanaTimeFilterTime(TIME_DEFAULTS, { + from: timeRange.from, + to: timeRange.to, + }); + + const [chartOptions, setChartOptions] = useStateWithLocalStorage( + 'MetricsExplorerChartOptions', + DEFAULT_CHART_OPTIONS + ); + const [isAutoReloading, setAutoReloading] = useState(false); + + const { metricThresholdPrefill } = useAlertPrefillContext(); + // For Jest compatibility; including metricThresholdPrefill as a dep in useEffect causes an + // infinite loop in test environment + const prefillContext = useMemo(() => metricThresholdPrefill, [metricThresholdPrefill]); + + useEffect(() => { + if (prefillContext) { + const { setPrefillOptions } = prefillContext; + const { metrics, groupBy, filterQuery } = options; + + setPrefillOptions({ metrics, groupBy, filterQuery }); + } + }, [options, prefillContext]); + + return { + defaultViewState: { + options: DEFAULT_OPTIONS, + chartOptions: DEFAULT_CHART_OPTIONS, + currentTimerange: timeRange, + }, + options, + chartOptions, + setChartOptions, + timeRange, + isAutoReloading, + setOptions, + setTimeRange, + startAutoReload: () => setAutoReloading(true), + stopAutoReload: () => setAutoReloading(false), + timestamps, + setTimestamps, + }; +}; + +export const [MetricsExplorerOptionsContainer, useMetricsExplorerOptionsContainerContext] = + createContainer(useMetricsExplorerOptions); diff --git a/x-pack/plugins/observability/public/pages/threshold/hooks/use_tracked_promise.ts b/x-pack/plugins/observability/public/pages/threshold/hooks/use_tracked_promise.ts new file mode 100644 index 0000000000000..d12749ea69fdc --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/hooks/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/observability/public/pages/threshold/i18n_strings.ts b/x-pack/plugins/observability/public/pages/threshold/i18n_strings.ts new file mode 100644 index 0000000000000..39d88710a3668 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/i18n_strings.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EQUATION_HELP_MESSAGE = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage', + { defaultMessage: 'Supports basic math expressions' } +); + +export const LABEL_LABEL = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel', + { defaultMessage: 'Label (optional)' } +); + +export const LABEL_HELP_MESSAGE = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage', + { + defaultMessage: 'Custom label will show on the alert chart and in reason/alert title', + } +); + +export const CUSTOM_EQUATION = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquation', + { + defaultMessage: 'Custom equation', + } +); + +export const DELETE_LABEL = i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton', + { defaultMessage: 'Delete' } +); + +export const AGGREGATION_LABEL = (name: string) => + i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel', + { + defaultMessage: 'Aggregation {name}', + values: { name }, + } + ); diff --git a/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.test.ts b/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.test.ts new file mode 100644 index 0000000000000..bafdf5f235467 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { MetricExpression } from '../types'; +import { generateUniqueKey } from './generate_unique_key'; + +describe('generateUniqueKey', () => { + const mockedCriteria: Array<[MetricExpression, string]> = [ + [ + { + aggType: Aggregators.COUNT, + comparator: Comparator.LT, + threshold: [2000, 5000], + timeSize: 15, + timeUnit: 'm', + }, + 'count<2000,5000', + ], + [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT_OR_EQ, + threshold: [30], + timeSize: 15, + timeUnit: 'm', + }, + 'custom>=30', + ], + [ + { + aggType: Aggregators.AVERAGE, + comparator: Comparator.LT_OR_EQ, + threshold: [500], + timeSize: 15, + timeUnit: 'm', + metric: 'metric', + }, + 'avg(metric)<=500', + ], + ]; + it.each(mockedCriteria)('unique key of %p is %s', (input, output) => { + const uniqueKey = generateUniqueKey(input); + + expect(uniqueKey).toBe(output); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.ts b/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.ts new file mode 100644 index 0000000000000..ec83311055a08 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/lib/generate_unique_key.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MetricExpression } from '../types'; + +export const generateUniqueKey = (criterion: MetricExpression) => { + const metric = criterion.metric ? `(${criterion.metric})` : ''; + + return criterion.aggType + metric + criterion.comparator + criterion.threshold.join(','); +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/lib/transform_metrics_explorer_data.ts b/x-pack/plugins/observability/public/pages/threshold/lib/transform_metrics_explorer_data.ts new file mode 100644 index 0000000000000..5fc221afca80f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/lib/transform_metrics_explorer_data.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { first } from 'lodash'; +import { MetricsExplorerResponse } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricThresholdAlertParams, ExpressionChartSeries } from '../types'; + +export const transformMetricsExplorerData = ( + params: MetricThresholdAlertParams, + data: MetricsExplorerResponse | null +) => { + const { criteria } = params; + const firstSeries = first(data?.series); + if (criteria && firstSeries) { + const series = firstSeries.rows.reduce((acc, row) => { + const { timestamp } = row; + criteria.forEach((item, index) => { + if (!acc[index]) { + acc[index] = []; + } + const value = (row[`metric_${index}`] as number) || 0; + acc[index].push({ timestamp, value }); + }); + return acc; + }, [] as ExpressionChartSeries); + return { id: firstSeries.id, series }; + } +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/observability/public/pages/threshold/mocks/metric_threshold_rule.ts new file mode 100644 index 0000000000000..aacc3790defdf --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/mocks/metric_threshold_rule.ts @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; + +import { MetricThresholdAlert, MetricThresholdRule } from '../components/alert_details_app_section'; + +export const buildMetricThresholdRule = ( + rule: Partial = {} +): MetricThresholdRule => { + return { + alertTypeId: 'metrics.alert.threshold', + createdBy: 'admin', + updatedBy: 'admin', + createdAt: new Date('2023-02-20T15:25:32.125Z'), + updatedAt: new Date('2023-03-02T16:24:41.177Z'), + apiKey: 'apiKey', + apiKeyOwner: 'admin', + notifyWhen: null, + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: [], + executionStatus: { + lastExecutionDate: new Date('2023-03-10T12:58:07.823Z'), + lastDuration: 3882, + status: 'ok', + }, + actions: [], + scheduledTaskId: 'cfd9c4f0-b132-11ed-88f2-77e0607bce49', + isSnoozedUntil: null, + lastRun: { + outcomeMsg: null, + outcomeOrder: 0, + alertsCount: { + new: 0, + ignored: 0, + recovered: 0, + active: 0, + }, + warning: null, + outcome: 'succeeded', + }, + nextRun: new Date('2023-03-10T12:59:07.592Z'), + id: uuidv4(), + consumer: 'alerts', + tags: [], + name: 'Monitoring hosts', + enabled: true, + throttle: null, + running: false, + schedule: { + interval: '1m', + }, + params: { + criteria: [ + { + aggType: Aggregators.COUNT, + comparator: Comparator.GT, + threshold: [2000], + timeSize: 15, + timeUnit: 'm', + }, + { + aggType: Aggregators.MAX, + comparator: Comparator.GT, + threshold: [4], + timeSize: 15, + timeUnit: 'm', + metric: 'system.cpu.user.pct', + warningComparator: Comparator.GT, + warningThreshold: [2.2], + }, + { + aggType: Aggregators.MIN, + comparator: Comparator.GT, + threshold: [0.8], + timeSize: 15, + timeUnit: 'm', + metric: 'system.memory.used.pct', + }, + ], + filterQuery: + '{"bool":{"filter":[{"bool":{"should":[{"term":{"host.hostname":{"value":"Users-System.local"}}}],"minimum_should_match":1}},{"bool":{"should":[{"term":{"service.type":{"value":"system"}}}],"minimum_should_match":1}}]}}', + groupBy: ['host.hostname'], + }, + monitoring: { + run: { + history: [ + { + duration: 4433, + success: true, + timestamp: 1678375661786, + }, + ], + calculated_metrics: { + success_ratio: 1, + p99: 7745, + p50: 4909.5, + p95: 6319, + }, + last_run: { + timestamp: '2023-03-10T12:58:07.823Z', + metrics: { + total_search_duration_ms: null, + total_indexing_duration_ms: null, + total_alerts_detected: null, + total_alerts_created: null, + gap_duration_s: null, + duration: 3882, + }, + }, + }, + }, + revision: 1, + ...rule, + }; +}; + +export const buildMetricThresholdAlert = ( + alert: Partial = {} +): MetricThresholdAlert => { + return { + link: '/app/metrics/explorer', + reason: 'system.cpu.user.pct reported no data in the last 1m for ', + fields: { + 'kibana.alert.rule.parameters': { + criteria: [ + { + aggType: Aggregators.AVERAGE, + comparator: Comparator.GT, + threshold: [2000], + timeSize: 15, + timeUnit: 'm', + metric: 'system.cpu.user.pct', + }, + { + aggType: Aggregators.MAX, + comparator: Comparator.GT, + threshold: [4], + timeSize: 15, + timeUnit: 'm', + metric: 'system.cpu.user.pct', + warningComparator: Comparator.GT, + warningThreshold: [2.2], + }, + ], + sourceId: 'default', + alertOnNoData: true, + alertOnGroupDisappear: true, + }, + 'kibana.alert.evaluation.values': [2500, 5], + 'kibana.alert.rule.category': 'Metric threshold', + 'kibana.alert.rule.consumer': 'alerts', + 'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7', + 'kibana.alert.rule.name': 'One condition', + 'kibana.alert.rule.producer': 'infrastructure', + 'kibana.alert.rule.rule_type_id': 'metrics.alert.threshold', + 'kibana.alert.rule.uuid': '3a1ed8c0-c1a8-11ed-9249-ed6d75986bdc', + 'kibana.space_ids': ['default'], + 'kibana.alert.rule.tags': [], + '@timestamp': '2023-03-28T14:40:00.000Z', + 'kibana.alert.reason': 'system.cpu.user.pct reported no data in the last 1m for ', + 'kibana.alert.action_group': 'metrics.threshold.nodata', + tags: [], + 'kibana.alert.duration.us': 248391946000, + 'kibana.alert.time_range': { + gte: '2023-03-13T14:06:23.695Z', + }, + 'kibana.alert.instance.id': '*', + 'kibana.alert.start': '2023-03-28T13:40:00.000Z', + 'kibana.alert.uuid': '50faddcd-c0a0-4122-a068-c204f4a7ec87', + 'kibana.alert.status': 'active', + 'kibana.alert.workflow_status': 'open', + 'event.kind': 'signal', + 'event.action': 'active', + 'kibana.version': '8.8.0', + 'kibana.alert.flapping': false, + 'kibana.alert.rule.revision': 1, + }, + active: true, + start: 1678716383695, + lastUpdated: 1678964775641, + ...alert, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/rule_data_formatters.ts b/x-pack/plugins/observability/public/pages/threshold/rule_data_formatters.ts new file mode 100644 index 0000000000000..ff5ba8262a92f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/rule_data_formatters.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 { ALERT_REASON } from '@kbn/rule-data-utils'; +import { ObservabilityRuleTypeFormatter } from '../..'; +// TODO: change +export const LINK_TO_METRICS_EXPLORER = '/app/metrics/explorer'; + +export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { + const reason = fields[ALERT_REASON] ?? '-'; + const link = LINK_TO_METRICS_EXPLORER; // TODO https://github.com/elastic/kibana/issues/106497 & https://github.com/elastic/kibana/issues/106958 + + return { + reason, + link, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/threshold/types.ts b/x-pack/plugins/observability/public/pages/threshold/types.ts new file mode 100644 index 0000000000000..6317b87ba2767 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/threshold/types.ts @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { CasesUiStart } from '@kbn/cases-plugin/public'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; +import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; +import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { + RuleTypeParams, + TriggersAndActionsUIPublicPluginStart, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { TimeUnitChar } from '../../../common/utils/formatters'; +import { MetricsExplorerSeries } from '../../../common/threshold_rule/metrics_explorer'; +import { + Comparator, + CustomMetricExpressionParams, + FilterQuery, + MetricExpressionParams, + MetricsSourceStatus, + NonCountMetricExpressionParams, + SnapshotCustomMetricInput, +} from '../../../common/threshold_rule/types'; +import { ObservabilityPublicStart } from '../../plugin'; +import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; + +export interface AlertContextMeta { + currentOptions?: Partial; + series?: MetricsExplorerSeries; +} + +export type MetricExpression = Omit< + MetricExpressionParams, + 'metric' | 'timeSize' | 'timeUnit' | 'metrics' | 'equation' | 'customMetrics' +> & { + metric?: NonCountMetricExpressionParams['metric']; + customMetrics?: CustomMetricExpressionParams['customMetrics']; + label?: CustomMetricExpressionParams['label']; + equation?: CustomMetricExpressionParams['equation']; + timeSize?: MetricExpressionParams['timeSize']; + timeUnit?: MetricExpressionParams['timeUnit']; +}; + +export enum AGGREGATION_TYPES { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', + P95 = 'p95', + P99 = 'p99', + CUSTOM = 'custom', +} + +export interface MetricThresholdAlertParams { + criteria?: MetricExpression[]; + groupBy?: string | string[]; + filterQuery?: string; + sourceId?: string; +} + +export interface ExpressionChartRow { + timestamp: number; + value: number; +} + +export type ExpressionChartSeries = ExpressionChartRow[][]; + +export interface TimeRange { + from?: string; + to?: string; +} + +export interface AlertParams { + criteria: MetricExpression[]; + groupBy?: string | string[]; + filterQuery?: FilterQuery; + sourceId: string; + filterQueryText?: string; + alertOnNoData?: boolean; + alertOnGroupDisappear?: boolean; + shouldDropPartialBuckets?: boolean; +} + +export interface InfraClientStartDeps { + cases: CasesUiStart; + charts: ChartsPluginStart; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + discover: DiscoverStart; + embeddable?: EmbeddableStart; + lens: LensPublicStart; + // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 + // ml: MlPluginStart; + observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; + osquery?: unknown; // OsqueryPluginStart; + share: SharePluginStart; + spaces: SpacesPluginStart; + storage: IStorageWrapper; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + uiActions: UiActionsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection: UsageCollectionStart; + // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 + // telemetry: ITelemetryClient; +} + +export type RendererResult = React.ReactElement | null; + +export type RendererFunction = (args: RenderArgs) => Result; +export interface DerivedIndexPattern { + fields: MetricsSourceStatus['indexFields']; + title: string; +} + +export const SnapshotMetricTypeKeys = { + count: null, + cpu: null, + diskLatency: null, + load: null, + memory: null, + memoryTotal: null, + tx: null, + rx: null, + logRate: null, + diskIOReadBytes: null, + diskIOWriteBytes: null, + s3TotalRequests: null, + s3NumberOfObjects: null, + s3BucketSize: null, + s3DownloadBytes: null, + s3UploadBytes: null, + rdsConnections: null, + rdsQueriesExecuted: null, + rdsActiveTransactions: null, + rdsLatency: null, + sqsMessagesVisible: null, + sqsMessagesDelayed: null, + sqsMessagesSent: null, + sqsMessagesEmpty: null, + sqsOldestMessage: null, + custom: null, +}; +export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); + +export type SnapshotMetricType = rt.TypeOf; +export interface InventoryMetricConditions { + metric: SnapshotMetricType; + timeSize: number; + timeUnit: TimeUnitChar; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + customMetric?: SnapshotCustomMetricInput; + warningThreshold?: number[]; + warningComparator?: Comparator; +} + +export interface MetricThresholdRuleTypeParams extends RuleTypeParams { + criteria: MetricExpressionParams[]; +} diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index b7dc5d77e799b..c7321deba167a 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -76,6 +76,9 @@ export interface ConfigSchema { enabled: boolean; }; }; + thresholdRule: { + enabled: boolean; + }; }; coPilot?: { enabled?: boolean; 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 213d5b4eaf768..73d394840d0d0 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 @@ -11,15 +11,20 @@ import { ALERT_REASON } from '@kbn/rule-data-utils'; import { SLO_ID_FIELD } from '../../common/field_names/infra_metrics'; import { ConfigSchema } from '../plugin'; import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry'; -import { SLO_BURN_RATE_RULE_ID } from '../../common/constants'; +import { + OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + SLO_BURN_RATE_RULE_TYPE_ID, +} from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; +import { validateMetricThreshold } from '../pages/threshold/components/validation'; +import { formatReason } from '../pages/threshold/rule_data_formatters'; export const registerObservabilityRuleTypes = ( config: ConfigSchema, observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry ) => { observabilityRuleTypeRegistry.register({ - id: SLO_BURN_RATE_RULE_ID, + id: SLO_BURN_RATE_RULE_TYPE_ID, description: i18n.translate('xpack.observability.slo.rules.burnRate.description', { defaultMessage: 'Alert when your SLO burn rate is too high over a defined period of time.', }), @@ -48,4 +53,36 @@ export const registerObservabilityRuleTypes = ( } ), }); + if (config.unsafe.thresholdRule.enabled) { + observabilityRuleTypeRegistry.register({ + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + description: i18n.translate( + 'xpack.observability.threshold.rule.alertFlyout.alertDescription', + { + defaultMessage: 'Alert when threshold breached.', + } + ), + iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.links.observability.metricsThreshold}`; + }, + ruleParamsExpression: lazy(() => import('../pages/threshold/components/expression')), + validate: validateMetricThreshold, + defaultActionMessage: i18n.translate( + 'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage', + { + defaultMessage: `\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\} is in a state of \\{\\{context.alertState\\}\\} + + Reason: + \\{\\{context.reason\\}\\} + `, + } + ), + requiresAppContext: false, + format: formatReason, + alertDetailsAppSection: lazy( + () => import('../pages/threshold/components/alert_details_app_section') + ), + }); + } }; diff --git a/x-pack/plugins/observability/public/test_utils/use_global_storybook_theme.tsx b/x-pack/plugins/observability/public/test_utils/use_global_storybook_theme.tsx new file mode 100644 index 0000000000000..2930d9de22346 --- /dev/null +++ b/x-pack/plugins/observability/public/test_utils/use_global_storybook_theme.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; 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 function GlobalStorybookThemeProviders({ + children, + storyContext, +}: { + storyContext: StoryContext; + children: React.ReactChild; +}) { + 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/observability/public/utils/kibana_react.storybook_decorator.tsx b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx index 5984815c3ca4d..4868a1e4e9d8b 100644 --- a/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx +++ b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx @@ -32,6 +32,7 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { metrics: { enabled: false }, uptime: { enabled: false }, }, + thresholdRule: { enabled: false }, }, coPilot: { enabled: false, diff --git a/x-pack/plugins/observability/public/utils/metrics_explorer.ts b/x-pack/plugins/observability/public/utils/metrics_explorer.ts new file mode 100644 index 0000000000000..ba8168a6f939e --- /dev/null +++ b/x-pack/plugins/observability/public/utils/metrics_explorer.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + MetricsExplorerResponse, + MetricsExplorerSeries, +} from '../../common/threshold_rule/metrics_explorer'; +import { + MetricsExplorerChartOptions, + MetricsExplorerChartType, + MetricsExplorerOptions, + MetricsExplorerTimeOptions, + MetricsExplorerTimestampsRT, + MetricsExplorerYAxisMode, +} from '../pages/threshold/hooks/use_metrics_explorer_options'; + +export const options: MetricsExplorerOptions = { + limit: 3, + groupBy: 'host.name', + aggregation: 'avg', + metrics: [{ aggregation: 'avg', field: 'system.cpu.user.pct' }], +}; + +export const source = { + name: 'default', + description: '', + metricAlias: 'metricbeat-*', + inventoryDefaultView: 'host', + metricsExplorerDefaultView: 'host', + fields: { + host: 'host.name', + container: 'container.id', + pod: 'kubernetes.pod.uid', + timestamp: '@timestamp', + message: ['message'], + tiebreaker: '@timestamp', + }, + anomalyThreshold: 20, +}; + +export const chartOptions: MetricsExplorerChartOptions = { + type: MetricsExplorerChartType.line, + yAxisMode: MetricsExplorerYAxisMode.fromZero, + stack: false, +}; + +export const derivedIndexPattern = { title: 'metricbeat-*', fields: [] }; + +export const timeRange: MetricsExplorerTimeOptions = { + from: 'now-1h', + to: 'now', + interval: '>=10s', +}; + +export const timestamps: MetricsExplorerTimestampsRT = { + fromTimestamp: 1678376367166, + toTimestamp: 1678379973620, + interval: '>=10s', +}; + +export const createSeries = (id: string): MetricsExplorerSeries => ({ + id, + columns: [ + { name: 'timestamp', type: 'date' }, + { name: 'metric_0', type: 'number' }, + { name: 'groupBy', type: 'string' }, + ], + rows: [ + { timestamp: 1, metric_0: 0.5, groupBy: id }, + { timestamp: 2, metric_0: 0.5, groupBy: id }, + { timestamp: 3, metric_0: 0.5, groupBy: id }, + ], +}); + +export const resp: MetricsExplorerResponse = { + pageInfo: { total: 10, afterKey: 'host-04' }, + series: [createSeries('host-01'), createSeries('host-02'), createSeries('host-03')], +}; diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index 9442b8fbcdccb..ad4f6b4670bde 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -36,6 +36,7 @@ const defaultConfig: ConfigSchema = { metrics: { enabled: false }, uptime: { enabled: false }, }, + thresholdRule: { enabled: false }, }, coPilot: { enabled: false, diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 4d2d651682a2d..fbdebcd8a7dee 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -41,6 +41,12 @@ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), }), }), + thresholdRule: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), + }), + thresholdRule: schema.object({ + groupByPageSize: schema.number({ defaultValue: 10_000 }), }), enabled: schema.boolean({ defaultValue: true }), coPilot: schema.maybe(observabilityCoPilotConfig), diff --git a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts index a8845d06354f2..6726c83217220 100644 --- a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts +++ b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts @@ -7,20 +7,83 @@ import { PluginSetupContract } from '@kbn/alerting-plugin/server'; import { IBasePath, Logger } from '@kbn/core/server'; -import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import { LocatorPublic } from '@kbn/share-plugin/common'; -import { AlertsLocatorParams } from '../../../common'; +import { + createLifecycleExecutor, + Dataset, + IRuleDataService, +} from '@kbn/rule-registry-plugin/server'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../common/constants'; +import { sloFeatureId, AlertsLocatorParams, observabilityFeatureId } from '../../../common'; +import { ObservabilityConfig } from '../..'; +import { SLO_RULE_REGISTRATION_CONTEXT } from '../../common/constants'; import { sloBurnRateRuleType } from './slo_burn_rate'; +import { thresholdRuleType } from './threshold/register_threshold_rule_type'; +import { sloRuleFieldMap } from './slo_burn_rate/field_map'; export function registerRuleTypes( alertingPlugin: PluginSetupContract, logger: Logger, - ruleDataClient: IRuleDataClient, + ruleDataService: IRuleDataService, basePath: IBasePath, + config: ObservabilityConfig, alertsLocator?: LocatorPublic ) { - const createLifecycleRuleExecutor = createLifecycleExecutor(logger.get('rules'), ruleDataClient); + // SLO RULE + const ruleDataClientSLO = ruleDataService.initializeIndex({ + feature: sloFeatureId, + registrationContext: SLO_RULE_REGISTRATION_CONTEXT, + dataset: Dataset.alerts, + componentTemplateRefs: [], + componentTemplates: [ + { + name: 'mappings', + mappings: mappingFromFieldMap( + { ...legacyExperimentalFieldMap, ...sloRuleFieldMap }, + 'strict' + ), + }, + ], + }); + + const createLifecycleRuleExecutorSLO = createLifecycleExecutor( + logger.get('rules'), + ruleDataClientSLO + ); alertingPlugin.registerType( - sloBurnRateRuleType(createLifecycleRuleExecutor, basePath, alertsLocator) + sloBurnRateRuleType(createLifecycleRuleExecutorSLO, basePath, alertsLocator) ); + + // Threshold RULE + if (config.unsafe.thresholdRule.enabled) { + const ruleDataClientThreshold = ruleDataService.initializeIndex({ + feature: observabilityFeatureId, + registrationContext: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + dataset: Dataset.alerts, + componentTemplateRefs: [], + componentTemplates: [ + { + name: 'mappings', + mappings: mappingFromFieldMap({ ...legacyExperimentalFieldMap }, 'strict'), + }, + ], + }); + + const createLifecycleRuleExecutorThreshold = createLifecycleExecutor( + logger.get('rules'), + ruleDataClientThreshold + ); + + alertingPlugin.registerType( + thresholdRuleType( + createLifecycleRuleExecutorThreshold, + basePath, + config, + logger, + ruleDataClientThreshold + ) + ); + } } diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index e87807c8a911e..18fd1fbc1bc5b 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -20,7 +20,7 @@ import { HIGH_PRIORITY_ACTION, LOW_PRIORITY_ACTION, MEDIUM_PRIORITY_ACTION, - SLO_BURN_RATE_RULE_ID, + SLO_BURN_RATE_RULE_TYPE_ID, } from '../../../../common/constants'; import { getRuleExecutor } from './executor'; @@ -48,7 +48,7 @@ export function sloBurnRateRuleType( alertsLocator?: LocatorPublic ) { return { - id: SLO_BURN_RATE_RULE_ID, + id: SLO_BURN_RATE_RULE_TYPE_ID, name: i18n.translate('xpack.observability.slo.rules.burnRate.name', { defaultMessage: 'SLO burn rate', }), diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts new file mode 100644 index 0000000000000..57f29f88a2313 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.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 { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import { isString, get, identity } from 'lodash'; +import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import type { BucketKey } from './get_data'; +import { calculateCurrentTimeframe, createBaseFilters } from './metric_query'; + +export interface MissingGroupsRecord { + key: string; + bucketKey: BucketKey; +} + +export const checkMissingGroups = async ( + esClient: ElasticsearchClient, + metricParams: MetricExpressionParams, + indexPattern: string, + groupBy: string | undefined | string[], + filterQuery: string | undefined, + logger: Logger, + timeframe: { start: number; end: number }, + missingGroups: MissingGroupsRecord[] = [] +): Promise => { + if (missingGroups.length === 0) { + return missingGroups; + } + const currentTimeframe = calculateCurrentTimeframe(metricParams, timeframe); + const baseFilters = createBaseFilters(metricParams, currentTimeframe, filterQuery); + const groupByFields = isString(groupBy) ? [groupBy] : groupBy ? groupBy : []; + + const searches = missingGroups.flatMap((group) => { + const groupByFilters = Object.values(group.bucketKey).map((key, index) => { + return { + match: { + [groupByFields[index]]: key, + }, + }; + }); + return [ + { index: indexPattern }, + { + size: 0, + terminate_after: 1, + track_total_hits: true, + query: { + bool: { + filter: [...baseFilters, ...groupByFilters], + }, + }, + }, + ]; + }); + + logger.trace(`Request: ${JSON.stringify({ searches })}`); + const response = await esClient.msearch({ searches }); + logger.trace(`Response: ${JSON.stringify(response)}`); + + const verifiedMissingGroups = response.responses + .map((resp, index) => { + const total = get(resp, 'hits.total.value', 0) as number; + if (!total) { + return missingGroups[index]; + } + return null; + }) + .filter(identity) as MissingGroupsRecord[]; + + return verifiedMissingGroups; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts new file mode 100644 index 0000000000000..efd5c1ff91534 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isString } from 'lodash'; +import { MissingGroupsRecord } from './check_missing_group'; + +export const convertStringsToMissingGroupsRecord = ( + missingGroups: Array +) => { + return missingGroups.map((subject) => { + if (isString(subject)) { + const parts = subject.split(','); + return { + key: subject, + bucketKey: parts.reduce((acc, part, index) => { + acc[`groupBy${index}`] = part; + return acc; + }, {} as Record), + }; + } + return subject; + }); +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts new file mode 100644 index 0000000000000..ff1c04ca5031a --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, + MetricExpressionParams, +} from '../../../../../common/threshold_rule/types'; +import { createConditionScript } from './create_condition_script'; +import { createLastPeriod } from './wrap_in_period'; + +const EMPTY_SHOULD_WARN = { + bucket_script: { + buckets_path: {}, + script: '0', + }, +}; + +export const createBucketSelector = ( + condition: MetricExpressionParams, + alertOnGroupDisappear: boolean = false, + groupBy?: string | string[], + lastPeriodEnd?: number +) => { + const hasGroupBy = groupBy != null; + const hasWarn = condition.warningThreshold != null && condition.warningComparator != null; + const isPercentile = [Aggregators.P95, Aggregators.P99].includes(condition.aggType); + const isCount = condition.aggType === Aggregators.COUNT; + const isRate = condition.aggType === Aggregators.RATE; + const bucketPath = isCount + ? "currentPeriod['all']>_count" + : isRate + ? `aggregatedValue` + : isPercentile + ? `currentPeriod[\'all\']>aggregatedValue[${ + condition.aggType === Aggregators.P95 ? '95' : '99' + }]` + : "currentPeriod['all']>aggregatedValue"; + + const shouldWarn = hasWarn + ? { + bucket_script: { + buckets_path: { + value: bucketPath, + }, + script: createConditionScript( + condition.warningThreshold as number[], + condition.warningComparator as Comparator + ), + }, + } + : EMPTY_SHOULD_WARN; + + const shouldTrigger = { + bucket_script: { + buckets_path: { + value: bucketPath, + }, + script: createConditionScript(condition.threshold, condition.comparator), + }, + }; + + const aggs: any = { + shouldWarn, + shouldTrigger, + }; + + if (hasGroupBy && alertOnGroupDisappear && lastPeriodEnd) { + const wrappedPeriod = createLastPeriod(lastPeriodEnd, condition); + aggs.lastPeriod = wrappedPeriod.lastPeriod; + aggs.missingGroup = { + bucket_script: { + buckets_path: { + lastPeriod: 'lastPeriod>_count', + currentPeriod: "currentPeriod['all']>_count", + }, + script: 'params.lastPeriod > 0 && params.currentPeriod < 1 ? 1 : 0', + }, + }; + aggs.newOrRecoveredGroup = { + bucket_script: { + buckets_path: { + lastPeriod: 'lastPeriod>_count', + currentPeriod: "currentPeriod['all']>_count", + }, + script: 'params.lastPeriod < 1 && params.currentPeriod > 0 ? 1 : 0', + }, + }; + } + + if (hasGroupBy) { + const evalutionBucketPath = + alertOnGroupDisappear && lastPeriodEnd + ? { + shouldWarn: 'shouldWarn', + shouldTrigger: 'shouldTrigger', + missingGroup: 'missingGroup', + newOrRecoveredGroup: 'newOrRecoveredGroup', + } + : { shouldWarn: 'shouldWarn', shouldTrigger: 'shouldTrigger' }; + + const evaluationScript = + alertOnGroupDisappear && lastPeriodEnd + ? '(params.missingGroup != null && params.missingGroup > 0) || (params.shouldWarn != null && params.shouldWarn > 0) || (params.shouldTrigger != null && params.shouldTrigger > 0) || (params.newOrRecoveredGroup != null && params.newOrRecoveredGroup > 0)' + : '(params.shouldWarn != null && params.shouldWarn > 0) || (params.shouldTrigger != null && params.shouldTrigger > 0)'; + + aggs.evaluation = { + bucket_selector: { + buckets_path: evalutionBucketPath, + script: evaluationScript, + }, + }; + } + + return aggs; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts new file mode 100644 index 0000000000000..2355b2d301065 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.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 { Comparator } from '../../../../../common/threshold_rule/types'; + +export const createConditionScript = (threshold: number[], comparator: Comparator) => { + if (comparator === Comparator.BETWEEN && threshold.length === 2) { + return { + source: `params.value > params.threshold0 && params.value < params.threshold1 ? 1 : 0`, + params: { + threshold0: threshold[0], + threshold1: threshold[1], + }, + }; + } + if (comparator === Comparator.OUTSIDE_RANGE && threshold.length === 2) { + return { + source: `params.value < params.threshold0 && params.value > params.threshold1 ? 1 : 0`, + params: { + threshold0: threshold[0], + threshold1: threshold[1], + }, + }; + } + return { + source: `params.value ${comparator} params.threshold ? 1 : 0`, + params: { + threshold: threshold[0], + }, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts new file mode 100644 index 0000000000000..21329d18a1074 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.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 { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { isEmpty } from 'lodash'; +import { MetricExpressionCustomMetric } from '../../../../../common/threshold_rule/types'; +import { MetricsExplorerCustomMetric } from './metrics_explorer'; + +const isMetricExpressionCustomMetric = ( + subject: MetricsExplorerCustomMetric | MetricExpressionCustomMetric +): subject is MetricExpressionCustomMetric => { + return (subject as MetricExpressionCustomMetric).aggType != null; +}; + +export const createCustomMetricsAggregations = ( + id: string, + customMetrics: Array, + equation?: string +) => { + const bucketsPath: { [id: string]: string } = {}; + const metricAggregations = customMetrics.reduce((acc, metric) => { + const key = `${id}_${metric.name}`; + const aggregation = isMetricExpressionCustomMetric(metric) + ? metric.aggType + : metric.aggregation; + + if (aggregation === 'count') { + bucketsPath[metric.name] = `${key}>_count`; + return { + ...acc, + [key]: { + filter: metric.filter + ? toElasticsearchQuery(fromKueryExpression(metric.filter)) + : { match_all: {} }, + }, + }; + } + + if (aggregation && metric.field) { + bucketsPath[metric.name] = key; + return { + ...acc, + [key]: { + [aggregation]: { field: metric.field }, + }, + }; + } + + return acc; + }, {}); + + if (isEmpty(metricAggregations)) { + return {}; + } + + return { + ...metricAggregations, + [id]: { + bucket_script: { + buckets_path: bucketsPath, + script: { + source: convertEquationToPainless(bucketsPath, equation), + lang: 'painless', + }, + }, + }, + }; +}; + +const convertEquationToPainless = (bucketsPath: { [id: string]: string }, equation?: string) => { + const workingEquation = equation || Object.keys(bucketsPath).join(' + '); + return Object.keys(bucketsPath).reduce((acc, key) => { + return acc.replace(key, `params.${key}`); + }, workingEquation); +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts new file mode 100644 index 0000000000000..d96dbe49f5a88 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.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 { Aggregators } from '../../../../../common/threshold_rule/types'; + +export const createPercentileAggregation = ( + type: Aggregators.P95 | Aggregators.P99, + field: string +) => { + const value = type === Aggregators.P95 ? 95 : 99; + return { + aggregatedValue: { + percentiles: { + field, + percents: [value], + keyed: true, + }, + }, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts new file mode 100644 index 0000000000000..74adc03ab83c3 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.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 moment from 'moment'; +import { TIMESTAMP_FIELD } from '../../../../../common/threshold_rule/constants'; +import { calculateRateTimeranges } from '../utils'; + +export const createRateAggsBucketScript = ( + timeframe: { start: number; end: number }, + id: string +) => { + const { intervalInSeconds } = calculateRateTimeranges({ + to: timeframe.end, + from: timeframe.start, + }); + return { + [id]: { + bucket_script: { + buckets_path: { + first: `currentPeriod['all']>${id}_first_bucket.maxValue`, + second: `currentPeriod['all']>${id}_second_bucket.maxValue`, + }, + script: `params.second > 0.0 && params.first > 0.0 && params.second > params.first ? (params.second - params.first) / ${intervalInSeconds}: null`, + }, + }, + }; +}; + +export const createRateAggsBuckets = ( + timeframe: { start: number; end: number }, + id: string, + field: string +) => { + const { firstBucketRange, secondBucketRange } = calculateRateTimeranges({ + to: timeframe.end, + from: timeframe.start, + }); + + return { + [`${id}_first_bucket`]: { + filter: { + range: { + [TIMESTAMP_FIELD]: { + gte: moment(firstBucketRange.from).toISOString(), + lt: moment(firstBucketRange.to).toISOString(), + }, + }, + }, + aggs: { maxValue: { max: { field } } }, + }, + [`${id}_second_bucket`]: { + filter: { + range: { + [TIMESTAMP_FIELD]: { + gte: moment(secondBucketRange.from).toISOString(), + lt: moment(secondBucketRange.to).toISOString(), + }, + }, + }, + aggs: { maxValue: { max: { field } } }, + }, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts new file mode 100644 index 0000000000000..06b1554706a93 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Aggregators } from '../../../../../common/threshold_rule/types'; +import moment from 'moment'; + +import { createTimerange } from './create_timerange'; + +describe('createTimerange(interval, aggType, timeframe)', () => { + describe('without timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return a second range for last 1 second', () => { + const subject = createTimerange(1000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(1000); + }); + it('should return a minute range for last 1 minute', () => { + const subject = createTimerange(60000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(60000); + }); + it('should return 5 minute range for last 5 minutes', () => { + const subject = createTimerange(300000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(300000); + }); + it('should return a hour range for last 1 hour', () => { + const subject = createTimerange(3600000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(3600000); + }); + it('should return a day range for last 1 day', () => { + const subject = createTimerange(86400000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(86400000); + }); + }); + describe('Rate Aggs', () => { + it('should return a 20 second range for last 1 second', () => { + const subject = createTimerange(1000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(1000 * 2); + }); + it('should return a 5 minute range for last 1 minute', () => { + const subject = createTimerange(60000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(60000 * 2); + }); + it('should return 25 minute range for last 5 minutes', () => { + const subject = createTimerange(300000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(300000 * 2); + }); + it('should return 5 hour range for last hour', () => { + const subject = createTimerange(3600000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(3600000 * 2); + }); + it('should return a 5 day range for last day', () => { + const subject = createTimerange(86400000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(86400000 * 2); + }); + }); + }); + describe('with full timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return 5 minute range when given 4 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(4, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.COUNT, timeframe); + expect(subject.end - subject.start).toEqual(300000); + }); + it('should return 6 minute range when given 6 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(6, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.COUNT, timeframe); + expect(subject.end - subject.start).toEqual(360000); + }); + }); + describe('Rate Aggs', () => { + it('should return 8 minute range when given 4 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(4, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject.end - subject.start).toEqual(300000 * 2); + }); + it('should return 12 minute range when given 6 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(6, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject.end - subject.start).toEqual(300000 * 2); + }); + }); + }); + describe('with partial timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return 5 minute range for last 5 minutes', () => { + const end = moment(); + const timeframe = { + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.AVERAGE, timeframe); + expect(subject).toEqual({ + start: end.clone().subtract(5, 'minutes').valueOf(), + end: end.valueOf(), + }); + }); + }); + describe('Rate Aggs', () => { + it('should return 10 minute range for last 5 minutes', () => { + const end = moment(); + const timeframe = { + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject).toEqual({ + start: end + .clone() + .subtract(300 * 2, 'seconds') + .valueOf(), + end: end.valueOf(), + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts new file mode 100644 index 0000000000000..446299c4ba92b --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.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 moment from 'moment'; +import { Aggregators } from '../../../../../common/threshold_rule/types'; + +export const createTimerange = ( + interval: number, + aggType: Aggregators, + timeframe?: { end: number; start?: number }, + lastPeriodEnd?: number +) => { + const to = moment(timeframe ? timeframe.end : Date.now()).valueOf(); + + // Rate aggregations need 5 buckets worth of data + const minimumBuckets = aggType === Aggregators.RATE ? 2 : 1; + const calculatedFrom = lastPeriodEnd ? lastPeriodEnd - interval : to - interval * minimumBuckets; + + // Use either the timeframe.start when the start is less then calculatedFrom + // OR use the calculatedFrom + const from = + timeframe && timeframe.start && timeframe.start <= calculatedFrom + ? timeframe.start + : calculatedFrom; + + return { start: from, end: to }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts new file mode 100644 index 0000000000000..0433b1a78e9b8 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import moment from 'moment'; +import type { Logger } from '@kbn/logging'; +import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { isCustom } from './metric_expression_params'; +import { getIntervalInSeconds } from '../utils'; +import { CUSTOM_EQUATION_I18N, DOCUMENT_COUNT_I18N } from '../messages'; +import { createTimerange } from './create_timerange'; +import { getData } from './get_data'; +import { checkMissingGroups, MissingGroupsRecord } from './check_missing_group'; +import { AdditionalContext } from '../utils'; + +export interface EvaluatedRuleParams { + criteria: MetricExpressionParams[]; + groupBy: string | undefined | string[]; + filterQuery?: string; + filterQueryText?: string; +} + +export type Evaluation = Omit & { + metric: string; + currentValue: number | null; + timestamp: string; + shouldFire: boolean; + shouldWarn: boolean; + isNoData: boolean; + bucketKey: Record; + context?: AdditionalContext; +}; + +export const evaluateRule = async ( + esClient: ElasticsearchClient, + params: Params, + dataView: string, + compositeSize: number, + alertOnGroupDisappear: boolean, + logger: Logger, + lastPeriodEnd?: number, + timeframe?: { start?: number; end: number }, + missingGroups: MissingGroupsRecord[] = [] +): Promise>> => { + const { criteria, groupBy, filterQuery } = params; + + return Promise.all( + criteria.map(async (criterion) => { + const interval = `${criterion.timeSize}${criterion.timeUnit}`; + const intervalAsSeconds = getIntervalInSeconds(interval); + const intervalAsMS = intervalAsSeconds * 1000; + const calculatedTimerange = createTimerange( + intervalAsMS, + criterion.aggType, + timeframe, + lastPeriodEnd + ); + + const currentValues = await getData( + esClient, + criterion, + dataView, + groupBy, + filterQuery, + compositeSize, + alertOnGroupDisappear, + calculatedTimerange, + logger, + lastPeriodEnd + ); + + const verifiedMissingGroups = await checkMissingGroups( + esClient, + criterion, + dataView, + groupBy, + filterQuery, + logger, + calculatedTimerange, + missingGroups + ); + + for (const missingGroup of verifiedMissingGroups) { + if (currentValues[missingGroup.key] == null) { + currentValues[missingGroup.key] = { + value: null, + trigger: false, + warn: false, + bucketKey: missingGroup.bucketKey, + }; + } + } + + const evaluations: Record = {}; + for (const key of Object.keys(currentValues)) { + const result = currentValues[key]; + if (result.trigger || result.warn || result.value === null) { + evaluations[key] = { + ...criterion, + metric: + criterion.aggType === 'count' + ? DOCUMENT_COUNT_I18N + : isCustom(criterion) && criterion.label + ? criterion.label + : criterion.aggType === 'custom' + ? CUSTOM_EQUATION_I18N + : criterion.metric, + currentValue: result.value, + timestamp: moment(calculatedTimerange.end).toISOString(), + shouldFire: result.trigger, + shouldWarn: result.warn, + isNoData: result.value === null, + bucketKey: result.bucketKey, + context: { + cloud: result.cloud, + host: result.host, + container: result.container, + orchestrator: result.orchestrator, + labels: result.labels, + tags: result.tags, + }, + }; + } + } + return evaluations; + }) + ); +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.ts new file mode 100644 index 0000000000000..7484273d96805 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.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. + */ + +import { SearchResponse, AggregationsAggregate } from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; +import { + Aggregators, + Comparator, + MetricExpressionParams, +} from '../../../../../common/threshold_rule/types'; + +import { + AdditionalContext, + doFieldsExist, + KUBERNETES_POD_UID, + termsAggField, + UNGROUPED_FACTORY_KEY, +} from '../utils'; +import { getElasticsearchMetricQuery } from './metric_query'; + +export type GetDataResponse = Record< + string, + { + warn: boolean; + trigger: boolean; + value: number | null; + bucketKey: BucketKey; + } & AdditionalContext +>; + +export type BucketKey = Record; +interface AggregatedValue { + value: number | null; + values?: Record; +} +interface Aggs { + currentPeriod: { + buckets: { + all: { + doc_count: number; + aggregatedValue?: AggregatedValue; + }; + }; + }; + aggregatedValue?: AggregatedValue; + shouldWarn?: { + value: number; + }; + shouldTrigger?: { + value: number; + }; + missingGroup?: { + value: number; + }; + containerContext?: ContainerContext; + additionalContext?: SearchResponse>; +} + +interface ContainerContext { + buckets: ContainerBucket[]; +} + +interface ContainerBucket { + key: BucketKey; + doc_count: number; + container: SearchResponse>; +} + +interface Bucket extends Aggs { + key: BucketKey; + doc_count: number; +} +interface ResponseAggregations extends Partial { + groupings?: { + after_key: Record; + buckets: Bucket[]; + }; + all?: { + buckets: { + all?: { + doc_count: number; + } & Aggs; + }; + }; +} + +const getValue = (aggregatedValue: AggregatedValue, params: MetricExpressionParams) => + [Aggregators.P95, Aggregators.P99].includes(params.aggType) && aggregatedValue.values != null + ? aggregatedValue.values[params.aggType === Aggregators.P95 ? '95.0' : '99.0'] + : aggregatedValue.value; + +const NO_DATA_RESPONSE = { + [UNGROUPED_FACTORY_KEY]: { + value: null, + warn: false, + trigger: false, + bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY }, + }, +}; + +const createContainerList = (containerContext: ContainerContext) => { + return containerContext.buckets + .map((bucket) => { + const containerHits = bucket.container.hits?.hits; + return containerHits?.length > 0 ? containerHits[0]._source?.container : undefined; + }) + .filter((container) => container !== undefined); +}; + +export const getData = async ( + esClient: ElasticsearchClient, + params: MetricExpressionParams, + index: string, + groupBy: string | undefined | string[], + filterQuery: string | undefined, + compositeSize: number, + alertOnGroupDisappear: boolean, + timeframe: { start: number; end: number }, + logger: Logger, + lastPeriodEnd?: number, + previousResults: GetDataResponse = {}, + afterKey?: Record +): Promise => { + const handleResponse = ( + aggs: ResponseAggregations, + previous: GetDataResponse, + successfulShards: number + ) => { + // This is absolutely NO DATA + if (successfulShards === 0) { + return NO_DATA_RESPONSE; + } + if (aggs.groupings) { + const { groupings } = aggs; + const nextAfterKey = groupings.after_key; + for (const bucket of groupings.buckets) { + const key = Object.values(bucket.key).join(','); + const { + shouldWarn, + shouldTrigger, + missingGroup, + currentPeriod, + aggregatedValue: aggregatedValueForRate, + additionalContext, + containerContext, + } = bucket; + + const { aggregatedValue, doc_count: docCount } = currentPeriod.buckets.all; + + const containerList = containerContext ? createContainerList(containerContext) : undefined; + + const bucketHits = additionalContext?.hits?.hits; + const additionalContextSource = + bucketHits && bucketHits.length > 0 ? bucketHits[0]._source : null; + + if (missingGroup && missingGroup.value > 0) { + previous[key] = { + trigger: false, + warn: false, + value: null, + bucketKey: bucket.key, + }; + } else { + const value = + params.aggType === Aggregators.COUNT + ? docCount + : params.aggType === Aggregators.RATE && aggregatedValueForRate != null + ? aggregatedValueForRate.value + : aggregatedValue != null + ? getValue(aggregatedValue, params) + : null; + + previous[key] = { + trigger: (shouldTrigger && shouldTrigger.value > 0) || false, + warn: (shouldWarn && shouldWarn.value > 0) || false, + value, + bucketKey: bucket.key, + container: containerList, + ...additionalContextSource, + }; + } + } + if (nextAfterKey) { + return getData( + esClient, + params, + index, + groupBy, + filterQuery, + compositeSize, + alertOnGroupDisappear, + timeframe, + logger, + lastPeriodEnd, + previous, + nextAfterKey + ); + } + return previous; + } + if (aggs.all?.buckets.all) { + const { + currentPeriod, + aggregatedValue: aggregatedValueForRate, + shouldWarn, + shouldTrigger, + } = aggs.all.buckets.all; + + const { aggregatedValue, doc_count: docCount } = currentPeriod.buckets.all; + + const value = + params.aggType === Aggregators.COUNT + ? docCount + : params.aggType === Aggregators.RATE && aggregatedValueForRate != null + ? aggregatedValueForRate.value + : aggregatedValue != null + ? getValue(aggregatedValue, params) + : null; + // There is an edge case where there is no results and the shouldWarn/shouldTrigger + // bucket scripts will be missing. This is only an issue for document count because + // the value will end up being ZERO, for other metrics it will be null. In this case + // we need to do the evaluation in Node.js + if (aggs.all && params.aggType === Aggregators.COUNT && value === 0) { + const trigger = comparatorMap[params.comparator](value, params.threshold); + const warn = + params.warningThreshold && params.warningComparator + ? comparatorMap[params.warningComparator](value, params.warningThreshold) + : false; + return { + [UNGROUPED_FACTORY_KEY]: { + value, + warn, + trigger, + bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY }, + }, + }; + } + return { + [UNGROUPED_FACTORY_KEY]: { + value, + warn: (shouldWarn && shouldWarn.value > 0) || false, + trigger: (shouldTrigger && shouldTrigger.value > 0) || false, + bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY }, + }, + }; + } else { + return NO_DATA_RESPONSE; + } + }; + + const fieldsExisted = groupBy?.includes(KUBERNETES_POD_UID) + ? await doFieldsExist(esClient, [termsAggField[KUBERNETES_POD_UID]], index) + : null; + + const request = { + index, + allow_no_indices: true, + ignore_unavailable: true, + body: getElasticsearchMetricQuery( + params, + timeframe, + compositeSize, + alertOnGroupDisappear, + lastPeriodEnd, + groupBy, + filterQuery, + afterKey, + fieldsExisted + ), + }; + logger.trace(`Request: ${JSON.stringify(request)}`); + const body = await esClient.search(request); + const { aggregations, _shards } = body; + logger.trace(`Response: ${JSON.stringify(body)}`); + if (aggregations) { + return handleResponse(aggregations, previousResults, _shards.successful); + } else if (_shards.successful) { + return previousResults; + } + return NO_DATA_RESPONSE; +}; + +const comparatorMap = { + [Comparator.BETWEEN]: (value: number, [a, b]: number[]) => + value >= Math.min(a, b) && value <= Math.max(a, b), + // `threshold` is always an array of numbers in case the BETWEEN comparator is + // used; all other compartors will just destructure the first value in the array + [Comparator.GT]: (a: number, [b]: number[]) => a > b, + [Comparator.LT]: (a: number, [b]: number[]) => a < b, + [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b, + [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b, + [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b, +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.ts new file mode 100644 index 0000000000000..c81b43c9baeb6 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.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 { + CustomMetricExpressionParams, + MetricExpressionParams, + NonCountMetricExpressionParams, +} from '../../../../../common/threshold_rule/types'; + +export const isNotCountOrCustom = ( + metricExpressionParams: MetricExpressionParams +): metricExpressionParams is NonCountMetricExpressionParams => { + const { aggType } = metricExpressionParams; + return aggType !== 'count' && aggType !== 'custom'; +}; + +export const isCustom = ( + metricExpressionParams: MetricExpressionParams +): metricExpressionParams is CustomMetricExpressionParams => { + const { aggType } = metricExpressionParams; + return aggType === 'custom'; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.ts new file mode 100644 index 0000000000000..f0380480c06c0 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.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 { + Comparator, + Aggregators, + MetricExpressionParams, +} from '../../../../../common/threshold_rule/types'; +import moment from 'moment'; +import { getElasticsearchMetricQuery } from './metric_query'; + +describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { + const expressionParams: MetricExpressionParams = { + metric: 'system.is.a.good.puppy.dog', + aggType: Aggregators.AVERAGE, + timeUnit: 'm', + timeSize: 1, + threshold: [1], + comparator: Comparator.GT, + }; + + const groupBy = 'host.doggoname'; + const timeframe = { + start: moment().subtract(5, 'minutes').valueOf(), + end: moment().valueOf(), + }; + + describe('when passed no filterQuery', () => { + const searchBody = getElasticsearchMetricQuery( + expressionParams, + timeframe, + 100, + true, + void 0, + groupBy + ); + test('includes a range filter', () => { + expect( + searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) + ).toBeTruthy(); + }); + + test('includes a metric field filter', () => { + expect(searchBody.query.bool.filter).toMatchObject( + expect.arrayContaining([{ exists: { field: 'system.is.a.good.puppy.dog' } }]) + ); + }); + }); + + describe('when passed a filterQuery', () => { + const filterQuery = + // This is adapted from a real-world query that previously broke alerts + // We want to make sure it doesn't override any existing filters + '{"bool":{"filter":[{"bool":{"filter":[{"bool":{"must_not":[{"bool":{"should":[{"query_string":{"query":"bark*","fields":["host.name^1.0"],"type":"best_fields","default_operator":"or","max_determinized_states":10000,"enable_position_increments":true,"fuzziness":"AUTO","fuzzy_prefix_length":0,"fuzzy_max_expansions":50,"phrase_slop":0,"escape":false,"auto_generate_synonyms_phrase_query":true,"fuzzy_transpositions":true,"boost":1}}],"adjust_pure_negative":true,"minimum_should_match":"1","boost":1}}],"adjust_pure_negative":true,"boost":1}},{"bool":{"must_not":[{"bool":{"should":[{"query_string":{"query":"woof*","fields":["host.name^1.0"],"type":"best_fields","default_operator":"or","max_determinized_states":10000,"enable_position_increments":true,"fuzziness":"AUTO","fuzzy_prefix_length":0,"fuzzy_max_expansions":50,"phrase_slop":0,"escape":false,"auto_generate_synonyms_phrase_query":true,"fuzzy_transpositions":true,"boost":1}}],"adjust_pure_negative":true,"minimum_should_match":"1","boost":1}}],"adjust_pure_negative":true,"boost":1}}],"adjust_pure_negative":true,"boost":1}}],"adjust_pure_negative":true,"boost":1}}'; + + const searchBody = getElasticsearchMetricQuery( + expressionParams, + timeframe, + 100, + true, + void 0, + groupBy, + filterQuery + ); + test('includes a range filter', () => { + expect( + searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) + ).toBeTruthy(); + }); + + test('includes a metric field filter', () => { + expect(searchBody.query.bool.filter).toMatchObject( + expect.arrayContaining([{ exists: { field: 'system.is.a.good.puppy.dog' } }]) + ); + }); + }); +}); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts new file mode 100644 index 0000000000000..e972334bc9ba4 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts @@ -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 moment from 'moment'; +import { Aggregators, MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { isCustom, isNotCountOrCustom } from './metric_expression_params'; +import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; +import { + hasAdditionalContext, + KUBERNETES_POD_UID, + NUMBER_OF_DOCUMENTS, + shouldTermsAggOnContainer, + termsAggField, + validGroupByForContext, +} from '../utils'; +import { createBucketSelector } from './create_bucket_selector'; +import { createPercentileAggregation } from './create_percentile_aggregation'; +import { createRateAggsBuckets, createRateAggsBucketScript } from './create_rate_aggregation'; +import { wrapInCurrentPeriod } from './wrap_in_period'; + +const getParsedFilterQuery: (filterQuery: string | undefined) => Array> = ( + filterQuery +) => { + if (!filterQuery) return []; + return [JSON.parse(filterQuery)]; +}; + +export const calculateCurrentTimeframe = ( + metricParams: MetricExpressionParams, + timeframe: { start: number; end: number } +) => ({ + ...timeframe, + start: moment(timeframe.end) + .subtract( + metricParams.aggType === Aggregators.RATE ? metricParams.timeSize * 2 : metricParams.timeSize, + metricParams.timeUnit + ) + .valueOf(), +}); + +export const createBaseFilters = ( + metricParams: MetricExpressionParams, + timeframe: { start: number; end: number }, + filterQuery?: string +) => { + const rangeFilters = [ + { + range: { + '@timestamp': { + gte: moment(timeframe.start).toISOString(), + lte: moment(timeframe.end).toISOString(), + }, + }, + }, + ]; + + const metricFieldFilters = + isNotCountOrCustom(metricParams) && metricParams.metric + ? [ + { + exists: { + field: metricParams.metric, + }, + }, + ] + : []; + + const parsedFilterQuery = getParsedFilterQuery(filterQuery); + + return [...rangeFilters, ...metricFieldFilters, ...parsedFilterQuery]; +}; + +export const getElasticsearchMetricQuery = ( + metricParams: MetricExpressionParams, + timeframe: { start: number; end: number }, + compositeSize: number, + alertOnGroupDisappear: boolean, + lastPeriodEnd?: number, + groupBy?: string | string[], + filterQuery?: string, + afterKey?: Record, + fieldsExisted?: Record | null +) => { + const { aggType } = metricParams; + if (isNotCountOrCustom(metricParams) && !metricParams.metric) { + throw new Error( + 'Can only aggregate without a metric if using the document count or custom aggregator' + ); + } + + // We need to make a timeframe that represents the current timeframe as oppose + // to the total timeframe (which includes the last period). + const currentTimeframe = calculateCurrentTimeframe(metricParams, timeframe); + + const metricAggregations = + aggType === Aggregators.COUNT + ? {} + : aggType === Aggregators.RATE + ? createRateAggsBuckets(currentTimeframe, 'aggregatedValue', metricParams.metric) + : aggType === Aggregators.P95 || aggType === Aggregators.P99 + ? createPercentileAggregation(aggType, metricParams.metric) + : isCustom(metricParams) + ? createCustomMetricsAggregations( + 'aggregatedValue', + metricParams.customMetrics, + metricParams.equation + ) + : { + aggregatedValue: { + [aggType]: { + field: metricParams.metric, + }, + }, + }; + + const bucketSelectorAggregations = createBucketSelector( + metricParams, + alertOnGroupDisappear, + groupBy, + lastPeriodEnd + ); + + const rateAggBucketScript = + metricParams.aggType === Aggregators.RATE + ? createRateAggsBucketScript(currentTimeframe, 'aggregatedValue') + : {}; + + const currentPeriod = wrapInCurrentPeriod(currentTimeframe, metricAggregations); + + const containerContextAgg = + shouldTermsAggOnContainer(groupBy) && + fieldsExisted && + fieldsExisted[termsAggField[KUBERNETES_POD_UID]] + ? { + containerContext: { + terms: { + field: termsAggField[KUBERNETES_POD_UID], + size: NUMBER_OF_DOCUMENTS, + }, + aggs: { + container: { + top_hits: { + size: 1, + _source: { + includes: ['container.*'], + }, + }, + }, + }, + }, + } + : void 0; + + const includesList = ['host.*', 'labels.*', 'tags', 'cloud.*', 'orchestrator.*']; + const excludesList = ['host.cpu.*', 'host.disk.*', 'host.network.*']; + if (!containerContextAgg) includesList.push('container.*'); + + const additionalContextAgg = hasAdditionalContext(groupBy, validGroupByForContext) + ? { + additionalContext: { + top_hits: { + size: 1, + _source: { + includes: includesList, + excludes: excludesList, + }, + }, + }, + } + : void 0; + + const aggs: any = groupBy + ? { + groupings: { + composite: { + size: compositeSize, + sources: Array.isArray(groupBy) + ? groupBy.map((field, index) => ({ + [`groupBy${index}`]: { + terms: { field }, + }, + })) + : [ + { + groupBy0: { + terms: { + field: groupBy, + }, + }, + }, + ], + }, + aggs: { + ...currentPeriod, + ...rateAggBucketScript, + ...bucketSelectorAggregations, + ...additionalContextAgg, + ...containerContextAgg, + }, + }, + } + : { + all: { + filters: { + filters: { + all: { + match_all: {}, + }, + }, + }, + aggs: { + ...currentPeriod, + ...rateAggBucketScript, + ...bucketSelectorAggregations, + }, + }, + }; + + if (aggs.groupings && afterKey) { + aggs.groupings.composite.after = afterKey; + } + + const baseFilters = createBaseFilters(metricParams, timeframe, filterQuery); + + return { + track_total_hits: true, + query: { + bool: { + filter: baseFilters, + }, + }, + size: 0, + aggs, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts new file mode 100644 index 0000000000000..2de1a21c3cd48 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { metricsExplorerCustomMetricAggregationRT } from '../../../../../common/threshold_rule/metrics_explorer'; + +export const METRIC_EXPLORER_AGGREGATIONS = [ + 'avg', + 'max', + 'min', + 'cardinality', + 'rate', + 'count', + 'sum', + 'p95', + 'p99', + 'custom', +] as const; + +type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; + +const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); + +export const metricsExplorerMetricRequiredFieldsRT = rt.type({ + aggregation: metricsExplorerAggregationRT, +}); + +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: metricsExplorerCustomMetricAggregationRT, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); + +export type MetricsExplorerCustomMetric = rt.TypeOf; + +export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ + field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, +}); + +export const metricsExplorerMetricRT = rt.intersection([ + metricsExplorerMetricRequiredFieldsRT, + metricsExplorerMetricOptionalFieldsRT, +]); + +export const timeRangeRT = rt.type({ + from: rt.number, + to: rt.number, + interval: rt.string, +}); + +export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ + timerange: timeRangeRT, + indexPattern: rt.string, + metrics: rt.array(metricsExplorerMetricRT), +}); + +const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); +export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); + +export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ + groupBy: rt.union([groupByRT, rt.array(groupByRT)]), + afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), + limit: rt.union([rt.number, rt.null, rt.undefined]), + filterQuery: rt.union([rt.string, rt.null, rt.undefined]), + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); + +export const metricsExplorerRequestBodyRT = rt.intersection([ + metricsExplorerRequestBodyRequiredFieldsRT, + metricsExplorerRequestBodyOptionalFieldsRT, +]); + +export const metricsExplorerPageInfoRT = rt.type({ + total: rt.number, + afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), +}); + +export const metricsExplorerColumnTypeRT = rt.keyof({ + date: null, + number: null, + string: null, +}); + +export const metricsExplorerColumnRT = rt.type({ + name: rt.string, + type: metricsExplorerColumnTypeRT, +}); + +export const metricsExplorerRowRT = rt.intersection([ + rt.type({ + timestamp: rt.number, + }), + rt.record( + rt.string, + rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) + ), +]); + +export const metricsExplorerSeriesRT = rt.intersection([ + rt.type({ + id: rt.string, + columns: rt.array(metricsExplorerColumnRT), + rows: rt.array(metricsExplorerRowRT), + }), + rt.partial({ + keys: rt.array(rt.string), + }), +]); + +export const metricsExplorerResponseRT = rt.type({ + series: rt.array(metricsExplorerSeriesRT), + pageInfo: metricsExplorerPageInfoRT, +}); + +export type AfterKey = rt.TypeOf; + +export type MetricsExplorerColumnType = rt.TypeOf; + +export type MetricsExplorerPageInfo = rt.TypeOf; + +export type MetricsExplorerColumn = rt.TypeOf; + +export type MetricsExplorerRow = rt.TypeOf; + +export type MetricsExplorerRequestBody = rt.TypeOf; + +export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts new file mode 100644 index 0000000000000..d53b76d169fb8 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { TIMESTAMP_FIELD } from '../../../../../common/threshold_rule/constants'; + +export const createLastPeriod = ( + lastPeriodEnd: number, + { timeUnit, timeSize }: MetricExpressionParams +) => { + const start = moment(lastPeriodEnd).subtract(timeSize, timeUnit).toISOString(); + return { + lastPeriod: { + filter: { + range: { + [TIMESTAMP_FIELD]: { + gte: start, + lte: moment(lastPeriodEnd).toISOString(), + }, + }, + }, + }, + }; +}; + +export const wrapInCurrentPeriod = ( + timeframe: { start: number; end: number }, + aggs: Aggs +) => { + return { + currentPeriod: { + filters: { + filters: { + all: { + range: { + [TIMESTAMP_FIELD]: { + gte: moment(timeframe.start).toISOString(), + lte: moment(timeframe.end).toISOString(), + }, + }, + }, + }, + }, + aggs, + }, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts new file mode 100644 index 0000000000000..2fc43883c0ed3 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/messages.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 { i18n } from '@kbn/i18n'; +import { Comparator } from '../../../../common/threshold_rule/types'; +import { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../../common'; +import { AlertStates } from './types'; +import { UNGROUPED_FACTORY_KEY } from './utils'; + +export const DOCUMENT_COUNT_I18N = i18n.translate( + 'xpack.observability.threshold.rule.threshold.documentCount', + { + defaultMessage: 'Document count', + } +); + +export const CUSTOM_EQUATION_I18N = i18n.translate( + 'xpack.observability.threshold.rule.threshold.customEquation', + { + defaultMessage: 'Custom equation', + } +); + +export const stateToAlertMessage = { + [AlertStates.ALERT]: i18n.translate('xpack.observability.threshold.rule.threshold.alertState', { + defaultMessage: 'ALERT', + }), + [AlertStates.WARNING]: i18n.translate( + 'xpack.observability.threshold.rule.threshold.warningState', + { + defaultMessage: 'WARNING', + } + ), + [AlertStates.NO_DATA]: i18n.translate( + 'xpack.observability.threshold.rule.threshold.noDataState', + { + defaultMessage: 'NO DATA', + } + ), + [AlertStates.ERROR]: i18n.translate('xpack.observability.threshold.rule.threshold.errorState', { + defaultMessage: 'ERROR', + }), + [AlertStates.OK]: i18n.translate('xpack.observability.threshold.rule.threshold.okState', { + defaultMessage: 'OK [Recovered]', + }), +}; + +const toNumber = (value: number | string) => + typeof value === 'string' ? parseFloat(value) : value; + +const recoveredComparatorToI18n = ( + comparator: Comparator, + threshold: number[], + currentValue: number +) => { + const belowText = i18n.translate('xpack.observability.threshold.rule.threshold.belowRecovery', { + defaultMessage: 'below', + }); + const aboveText = i18n.translate('xpack.observability.threshold.rule.threshold.aboveRecovery', { + defaultMessage: 'above', + }); + switch (comparator) { + case Comparator.BETWEEN: + return currentValue < threshold[0] ? belowText : aboveText; + case Comparator.OUTSIDE_RANGE: + return i18n.translate('xpack.observability.threshold.rule.threshold.betweenRecovery', { + defaultMessage: 'between', + }); + case Comparator.GT: + case Comparator.GT_OR_EQ: + return belowText; + case Comparator.LT: + case Comparator.LT_OR_EQ: + return aboveText; + } +}; + +const thresholdToI18n = ([a, b]: Array) => { + if (typeof b === 'undefined') return a; + return i18n.translate('xpack.observability.threshold.rule.threshold.thresholdRange', { + defaultMessage: '{a} and {b}', + values: { a, b }, + }); +}; + +const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? '' : ` for ${group}`); + +export const buildFiredAlertReason: (alertResult: { + group: string; + metric: string; + comparator: Comparator; + threshold: Array; + currentValue: number | string; + timeSize: number; + timeUnit: TimeUnitChar; +}) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) => + i18n.translate('xpack.observability.threshold.rule.threshold.firedAlertReason', { + defaultMessage: + '{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.', + values: { + group: formatGroup(group), + metric, + comparator, + threshold: thresholdToI18n(threshold), + currentValue, + duration: formatDurationFromTimeUnitChar(timeSize, timeUnit), + }, + }); + +// Once recovered reason messages are re-enabled, checkout this issue https://github.com/elastic/kibana/issues/121272 regarding latest reason format +export const buildRecoveredAlertReason: (alertResult: { + group: string; + metric: string; + comparator: Comparator; + threshold: Array; + currentValue: number | string; +}) => string = ({ group, metric, comparator, threshold, currentValue }) => + i18n.translate('xpack.observability.threshold.rule.threshold.recoveredAlertReason', { + defaultMessage: + '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', + values: { + metric, + comparator: recoveredComparatorToI18n( + comparator, + threshold.map(toNumber), + toNumber(currentValue) + ), + threshold: thresholdToI18n(threshold), + currentValue, + group, + }, + }); + +export const buildNoDataAlertReason: (alertResult: { + group: string; + metric: string; + timeSize: number; + timeUnit: string; +}) => string = ({ group, metric, timeSize, timeUnit }) => + i18n.translate('xpack.observability.threshold.rule.threshold.noDataAlertReason', { + defaultMessage: '{metric} reported no data in the last {interval}{group}', + values: { + metric, + interval: `${timeSize}${timeUnit}`, + group: formatGroup(group), + }, + }); + +export const buildErrorAlertReason = (metric: string) => + i18n.translate('xpack.observability.threshold.rule.threshold.errorAlertReason', { + defaultMessage: 'Elasticsearch failed when attempting to query data for {metric}', + values: { + metric, + }, + }); + +export const buildInvalidQueryAlertReason = (filterQueryText: string) => + i18n.translate('xpack.observability.threshold.rule.threshold.queryErrorAlertReason', { + defaultMessage: 'Alert is using a malformed KQL query: {filterQueryText}', + values: { + filterQueryText, + }, + }); + +export const groupByKeysActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.groupByKeysActionVariableDescription', + { + defaultMessage: 'The object containing groups that are reporting data', + } +); + +export const alertStateActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.alertStateActionVariableDescription', + { + defaultMessage: 'Current state of the alert', + } +); + +export const alertDetailUrlActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription', + { + defaultMessage: + 'Link to the view within Elastic that shows further details and context surrounding this alert', + } +); + +export const reasonActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.reasonActionVariableDescription', + { + defaultMessage: 'A concise description of the reason for the alert', + } +); + +export const timestampActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.timestampDescription', + { + defaultMessage: 'A timestamp of when the alert was detected.', + } +); + +export const valueActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.valueActionVariableDescription', + { + defaultMessage: + 'The value of the metric in the specified condition. Usage: (ctx.value.condition0, ctx.value.condition1, etc...).', + } +); + +export const metricActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.metricActionVariableDescription', + { + defaultMessage: + 'The metric name in the specified condition. Usage: (ctx.metric.condition0, ctx.metric.condition1, etc...).', + } +); + +export const thresholdActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.thresholdActionVariableDescription', + { + defaultMessage: + 'The threshold value of the metric for the specified condition. Usage: (ctx.threshold.condition0, ctx.threshold.condition1, etc...).', + } +); + +export const viewInAppUrlActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription', + { + defaultMessage: + 'Link to the view or feature within Elastic that can assist with further investigation', + } +); + +export const cloudActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.cloudActionVariableDescription', + { + defaultMessage: 'The cloud object defined by ECS if available in the source.', + } +); + +export const hostActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.hostActionVariableDescription', + { + defaultMessage: 'The host object defined by ECS if available in the source.', + } +); + +export const containerActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.containerActionVariableDescription', + { + defaultMessage: 'The container object defined by ECS if available in the source.', + } +); + +export const orchestratorActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.orchestratorActionVariableDescription', + { + defaultMessage: 'The orchestrator object defined by ECS if available in the source.', + } +); + +export const labelsActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.labelsActionVariableDescription', + { + defaultMessage: 'List of labels associated with the entity where this alert triggered.', + } +); + +export const tagsActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.tagsActionVariableDescription', + { + defaultMessage: 'List of tags associated with the entity where this alert triggered.', + } +); + +export const originalAlertStateActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.originalAlertStateActionVariableDescription', + { + defaultMessage: + 'The state of the alert before it recovered. This is only available in the recovery context', + } +); + +export const originalAlertStateWasActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.originalAlertStateWasWARNINGActionVariableDescription', + { + defaultMessage: + 'Boolean value of the state of the alert before it recovered. This can be used for template conditions. This is only available in the recovery context', + } +); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts new file mode 100644 index 0000000000000..f61b8d17eef31 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts @@ -0,0 +1,224 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { ActionGroupIdsOf } from '@kbn/alerting-plugin/common'; +import { IRuleTypeAlerts, RuleType } from '@kbn/alerting-plugin/server'; +import { IBasePath, Logger } from '@kbn/core/server'; +import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; +import { + createGetSummarizedAlertsFn, + createLifecycleExecutor, + IRuleDataClient, +} from '@kbn/rule-registry-plugin/server'; +import { LicenseType } from '@kbn/licensing-plugin/server'; +import { observabilityFeatureId } from '../../../../common'; +import { Comparator } from '../../../../common/threshold_rule/types'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; + +import { + alertDetailUrlActionVariableDescription, + alertStateActionVariableDescription, + cloudActionVariableDescription, + containerActionVariableDescription, + groupByKeysActionVariableDescription, + hostActionVariableDescription, + labelsActionVariableDescription, + metricActionVariableDescription, + orchestratorActionVariableDescription, + originalAlertStateActionVariableDescription, + originalAlertStateWasActionVariableDescription, + reasonActionVariableDescription, + tagsActionVariableDescription, + thresholdActionVariableDescription, + timestampActionVariableDescription, + valueActionVariableDescription, + viewInAppUrlActionVariableDescription, +} from './messages'; +import { + getAlertDetailsPageEnabledForApp, + oneOfLiterals, + validateIsStringElasticsearchJSONFilter, +} from './utils'; +import { + createMetricThresholdExecutor, + FIRED_ACTIONS, + WARNING_ACTIONS, + NO_DATA_ACTIONS, +} from './threshold_executor'; +import { ObservabilityConfig } from '../../..'; +import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/threshold_rule/constants'; + +export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { + context: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + mappings: { fieldMap: legacyExperimentalFieldMap }, + useEcs: true, + useLegacyAlerts: false, +}; + +type MetricThresholdAllowedActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS | typeof NO_DATA_ACTIONS +>; +export type MetricThresholdAlertType = Omit & { + ActionGroupIdsOf: MetricThresholdAllowedActionGroups; +}; +type CreateLifecycleExecutor = ReturnType; + +export function thresholdRuleType( + createLifecycleRuleExecutor: CreateLifecycleExecutor, + basePath: IBasePath, + config: ObservabilityConfig, + logger: Logger, + ruleDataClient: IRuleDataClient +) { + const baseCriterion = { + threshold: schema.arrayOf(schema.number()), + comparator: oneOfLiterals(Object.values(Comparator)), + timeUnit: schema.string(), + timeSize: schema.number(), + warningThreshold: schema.maybe(schema.arrayOf(schema.number())), + warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))), + }; + + const nonCountCriterion = schema.object({ + ...baseCriterion, + metric: schema.string(), + aggType: oneOfLiterals(METRIC_EXPLORER_AGGREGATIONS), + customMetrics: schema.never(), + equation: schema.never(), + label: schema.never(), + }); + + const countCriterion = schema.object({ + ...baseCriterion, + aggType: schema.literal('count'), + metric: schema.never(), + customMetrics: schema.never(), + equation: schema.never(), + label: schema.never(), + }); + + const customCriterion = schema.object({ + ...baseCriterion, + aggType: schema.literal('custom'), + metric: schema.never(), + customMetrics: schema.arrayOf( + schema.oneOf([ + schema.object({ + name: schema.string(), + aggType: oneOfLiterals(['avg', 'sum', 'max', 'min', 'cardinality']), + field: schema.string(), + filter: schema.never(), + }), + schema.object({ + name: schema.string(), + aggType: schema.literal('count'), + filter: schema.maybe(schema.string()), + field: schema.never(), + }), + ]) + ), + equation: schema.maybe(schema.string()), + label: schema.maybe(schema.string()), + }); + const getSummarizedAlerts = createGetSummarizedAlertsFn({ + ruleDataClient, + useNamespace: false, + isLifecycleAlert: false, + }); + + const groupActionVariableDescription = i18n.translate( + 'xpack.observability.threshold.rule.alerting.groupActionVariableDescription', + { + defaultMessage: + 'Name of the group(s) reporting data. For accessing each group key, use context.groupByKeys.', + } + ); + + return { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: i18n.translate('xpack.observability.threshold.ruleName', { + defaultMessage: 'Threshold', + }), + validate: { + params: schema.object( + { + criteria: schema.arrayOf( + schema.oneOf([countCriterion, nonCountCriterion, customCriterion]) + ), + groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + filterQuery: schema.maybe( + schema.string({ + validate: validateIsStringElasticsearchJSONFilter, + }) + ), + sourceId: schema.string(), + alertOnNoData: schema.maybe(schema.boolean()), + alertOnGroupDisappear: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ), + }, + defaultActionGroupId: FIRED_ACTIONS.id, + actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS, NO_DATA_ACTIONS], + minimumLicenseRequired: 'basic' as LicenseType, + isExportable: true, + executor: createLifecycleRuleExecutor( + createMetricThresholdExecutor({ basePath, logger, config }) + ), + doesSetRecoveryContext: true, + actionVariables: { + context: [ + { name: 'group', description: groupActionVariableDescription }, + { name: 'groupByKeys', description: groupByKeysActionVariableDescription }, + ...(getAlertDetailsPageEnabledForApp(config.unsafe.alertDetails, 'metrics') + ? [ + { + name: 'alertDetailsUrl', + description: alertDetailUrlActionVariableDescription, + usesPublicBaseUrl: true, + }, + ] + : []), + { name: 'alertState', description: alertStateActionVariableDescription }, + { name: 'reason', description: reasonActionVariableDescription }, + { name: 'timestamp', description: timestampActionVariableDescription }, + { name: 'value', description: valueActionVariableDescription }, + { name: 'metric', description: metricActionVariableDescription }, + { name: 'threshold', description: thresholdActionVariableDescription }, + { + name: 'viewInAppUrl', + description: viewInAppUrlActionVariableDescription, + usesPublicBaseUrl: true, + }, + { name: 'cloud', description: cloudActionVariableDescription }, + { name: 'host', description: hostActionVariableDescription }, + { name: 'container', description: containerActionVariableDescription }, + { name: 'orchestrator', description: orchestratorActionVariableDescription }, + { name: 'labels', description: labelsActionVariableDescription }, + { name: 'tags', description: tagsActionVariableDescription }, + { name: 'originalAlertState', description: originalAlertStateActionVariableDescription }, + { + name: 'originalAlertStateWasALERT', + description: originalAlertStateWasActionVariableDescription, + }, + { + name: 'originalAlertStateWasWARNING', + description: originalAlertStateWasActionVariableDescription, + }, + { + name: 'originalAlertStateWasNO_DATA', + description: originalAlertStateWasActionVariableDescription, + }, + ], + }, + producer: observabilityFeatureId, + getSummarizedAlerts: getSummarizedAlerts(), + alerts: MetricsRulesTypeAlertDefinition, + }; +} diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/test_mocks.ts b/x-pack/plugins/observability/server/lib/rules/threshold/test_mocks.ts new file mode 100644 index 0000000000000..3d3c7a17cd1dd --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/test_mocks.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const bucketsA = (from: number) => [ + { + doc_count: null, + aggregatedValue: { value: null, values: [{ key: 95.0, value: null }] }, + from_as_string: new Date(from).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] }, + from_as_string: new Date(from + 60000).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] }, + from_as_string: new Date(from + 120000).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] }, + from_as_string: new Date(from + 180000).toISOString(), + }, + { + doc_count: 3, + aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] }, + from_as_string: new Date(from + 240000).toISOString(), + }, + { + doc_count: 1, + aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] }, + from_as_string: new Date(from + 300000).toISOString(), + }, +]; + +const bucketsB = (from: number) => [ + { + doc_count: 0, + aggregatedValue: { value: null, values: [{ key: 99.0, value: null }] }, + from_as_string: new Date(from).toISOString(), + }, + { + doc_count: 4, + aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] }, + from_as_string: new Date(from + 60000).toISOString(), + }, + { + doc_count: 4, + aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] }, + from_as_string: new Date(from + 120000).toISOString(), + }, + { + doc_count: 4, + aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] }, + from_as_string: new Date(from + 180000).toISOString(), + }, + { + doc_count: 5, + aggregatedValue: { value: 3.5, values: [{ key: 99.0, value: 3.5 }] }, + from_as_string: new Date(from + 240000).toISOString(), + }, + { + doc_count: 1, + aggregatedValue: { value: 3, values: [{ key: 99.0, value: 3 }] }, + from_as_string: new Date(from + 300000).toISOString(), + }, +]; + +const bucketsC = (from: number) => [ + { + doc_count: 0, + aggregatedValue: { value: null }, + from_as_string: new Date(from).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5 }, + from_as_string: new Date(from + 60000).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5 }, + from_as_string: new Date(from + 120000).toISOString(), + }, + { + doc_count: 2, + aggregatedValue: { value: 0.5 }, + from_as_string: new Date(from + 180000).toISOString(), + }, + { + doc_count: 3, + aggregatedValue: { value: 16 }, + from_as_string: new Date(from + 240000).toISOString(), + }, + { + doc_count: 1, + aggregatedValue: { value: 3 }, + from_as_string: new Date(from + 300000).toISOString(), + }, +]; + +export const basicMetricResponse = () => ({ + hits: { + total: { + value: 1, + }, + }, + aggregations: { + aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] }, + }, +}); + +export const alternateMetricResponse = () => ({ + hits: { + total: { + value: 1, + }, + }, + aggregations: { + aggregatedValue: { value: 3, values: [{ key: 99.0, value: 3 }] }, + }, +}); + +export const emptyMetricResponse = { + aggregations: { + aggregatedIntervals: { + buckets: [], + }, + }, +}; + +export const emptyRateResponse = (from: number) => ({ + aggregations: { + aggregatedIntervals: { + buckets: [ + { + doc_count: 2, + aggregatedValueMax: { value: null }, + from_as_string: new Date(from).toISOString(), + }, + ], + }, + }, +}); + +export const basicCompositeResponse = (from: number) => ({ + aggregations: { + groupings: { + after_key: { groupBy0: 'foo' }, + buckets: [ + { + key: { + groupBy0: 'a', + }, + aggregatedIntervals: { + buckets: bucketsA(from), + }, + doc_count: 1, + }, + { + key: { + groupBy0: 'b', + }, + aggregatedIntervals: { + buckets: bucketsB(from), + }, + doc_count: 1, + }, + ], + }, + }, + hits: { + total: { + value: 2, + }, + }, +}); + +export const alternateCompositeResponse = (from: number) => ({ + aggregations: { + groupings: { + after_key: { groupBy0: 'foo' }, + buckets: [ + { + key: { + groupBy0: 'a', + }, + aggregatedIntervals: { + buckets: bucketsB(from), + }, + doc_count: 1, + }, + { + key: { + groupBy0: 'b', + }, + aggregatedIntervals: { + buckets: bucketsA(from), + }, + doc_count: 1, + }, + { + key: { + groupBy0: 'c', + }, + aggregatedIntervals: { + buckets: bucketsC(from), + }, + doc_count: 1, + }, + ], + }, + }, + hits: { + total: { + value: 3, + }, + }, +}); + +export const compositeEndResponse = { + aggregations: {}, + hits: { total: { value: 0 } }, +}; + +export const changedSourceIdResponse = (from: number) => ({ + aggregations: { + aggregatedIntervals: { + buckets: bucketsC(from), + }, + }, +}); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts new file mode 100644 index 0000000000000..55494d0b1264e --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts @@ -0,0 +1,2022 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AlertInstanceContext as AlertContext, + AlertInstanceState as AlertState, +} from '@kbn/alerting-plugin/server'; +import { + AlertInstanceMock, + RuleExecutorServicesMock, + alertsMock, +} from '@kbn/alerting-plugin/server/mocks'; +import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import { + createMetricThresholdExecutor, + FIRED_ACTIONS, + NO_DATA_ACTIONS, + WARNING_ACTIONS, +} from './threshold_executor'; +import { Evaluation } from './lib/evaluate_rule'; +import type { LogMeta, Logger } from '@kbn/logging'; +import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { + Aggregators, + Comparator, + CountMetricExpressionParams, + NonCountMetricExpressionParams, +} from '../../../../common/threshold_rule/types'; + +jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} + +let persistAlertInstances = false; // eslint-disable-line prefer-const + +type TestRuleState = Record & { + aRuleStateKey: string; + groups: string[]; + groupBy?: string | string[]; +}; + +const initialRuleState: TestRuleState = { + aRuleStateKey: 'INITIAL_RULE_STATE_VALUE', + groups: [], +}; + +const fakeLogger = (msg: string, meta?: Meta) => {}; + +const logger = { + trace: fakeLogger, + debug: fakeLogger, + info: fakeLogger, + warn: fakeLogger, + error: fakeLogger, + fatal: fakeLogger, + log: () => void 0, + get: () => logger, +} as unknown as Logger; + +const STARTED_AT_MOCK_DATE = new Date(); + +const mockOptions = { + executionId: '', + startedAt: STARTED_AT_MOCK_DATE, + previousStartedAt: null, + state: { + wrapped: initialRuleState, + trackedAlerts: { + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + started: '2020-01-01T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + started: '2020-01-02T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + }, + trackedAlertsRecovered: {}, + }, + spaceId: '', + rule: { + id: '', + name: '', + tags: [], + consumer: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + producer: '', + revision: 0, + ruleTypeId: '', + ruleTypeName: '', + muteAll: false, + snoozeSchedule: [], + }, + logger, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, +}; + +const setEvaluationResults = (response: Array>) => { + jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response); +}; + +// FAILING: https://github.com/elastic/kibana/issues/155534 +describe.skip('The metric threshold alert type', () => { + describe('querying the entire infrastructure', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + }, + ], + }, + }); + const setResults = ( + comparator: Comparator, + threshold: number[], + shouldFire: boolean = false, + shouldWarn: boolean = false, + isNoData: boolean = false + ) => + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator, + threshold, + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire, + shouldWarn, + isNoData, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + test('alerts as expected with the > comparator', async () => { + setResults(Comparator.GT, [0.75], true); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT, [1.5], false); + await execute(Comparator.GT, [1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the < comparator', async () => { + setResults(Comparator.LT, [1.5], true); + await execute(Comparator.LT, [1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT, [0.75], false); + await execute(Comparator.LT, [0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the >= comparator', async () => { + setResults(Comparator.GT_OR_EQ, [0.75], true); + await execute(Comparator.GT_OR_EQ, [0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT_OR_EQ, [1.0], true); + await execute(Comparator.GT_OR_EQ, [1.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT_OR_EQ, [1.5], false); + await execute(Comparator.GT_OR_EQ, [1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the <= comparator', async () => { + setResults(Comparator.LT_OR_EQ, [1.5], true); + await execute(Comparator.LT_OR_EQ, [1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT_OR_EQ, [1.0], true); + await execute(Comparator.LT_OR_EQ, [1.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT_OR_EQ, [0.75], false); + await execute(Comparator.LT_OR_EQ, [0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the between comparator', async () => { + setResults(Comparator.BETWEEN, [0, 1.5], true); + await execute(Comparator.BETWEEN, [0, 1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.BETWEEN, [0, 0.75], false); + await execute(Comparator.BETWEEN, [0, 0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the outside range comparator', async () => { + setResults(Comparator.OUTSIDE_RANGE, [0, 0.75], true); + await execute(Comparator.OUTSIDE_RANGE, [0, 0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.OUTSIDE_RANGE, [0, 1.5], false); + await execute(Comparator.OUTSIDE_RANGE, [0, 1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('reports expected values to the action context', async () => { + setResults(Comparator.GT, [0.75], true); + await execute(Comparator.GT, [0.75]); + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toContain('is 1'); + expect(action.reason).toContain('Alert when > 0.75'); + expect(action.reason).toContain('test.metric.1'); + expect(action.reason).toContain('in the last 1 min'); + }); + }); + + describe('querying with a groupBy parameter', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['something'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + }); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + test('sends an alert when all groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + }); + test('sends an alert when only some groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1.5], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1.5], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.LT, [1.5]); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('sends no alert when no groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [5], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [5], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [5]); + expect(mostRecentAction(instanceIdA)).toBe(undefined); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('reports group values to the action context', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.group).toBe('a'); + expect(mostRecentAction(instanceIdB).action.group).toBe('b'); + }); + test('persists previous groups that go missing, until the groupBy param changes', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult1 } = await execute( + Comparator.GT, + [0.75], + ['something'], + 'test.metric.2' + ); + expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult2 } = await execute( + Comparator.GT, + [0.75], + ['something'], + 'test.metric.1', + stateResult1 + ); + expect(stateResult2.missingGroups).toEqual( + expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) + ); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const { state: stateResult3 } = await execute( + Comparator.GT, + [0.75], + ['something', 'something-else'], + 'test.metric.1', + stateResult2 + ); + expect(stateResult3.missingGroups).toEqual(expect.arrayContaining([])); + }); + + const executeWithFilter = ( + comparator: Comparator, + threshold: number[], + filterQuery: string, + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy: ['something'], + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + filterQuery, + }, + state: state ?? mockOptions.state.wrapped, + }); + test('persists previous groups that go missing, until the filterQuery param changes', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult1 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'q' }), + 'test.metric.2' + ); + expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult2 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'q' }), + 'test.metric.1', + stateResult1 + ); + expect(stateResult2.missingGroups).toEqual( + expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) + ); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const { state: stateResult3 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'different' }), + 'test.metric.1', + stateResult2 + ); + expect(stateResult3.groups).toEqual(expect.arrayContaining([])); + }); + }); + + describe('querying with a groupBy parameter host.name and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['host.name'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('rule tags and source tags are combined in alert context', async () => { + setEvaluationResults([ + { + 'host-01': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-01' }, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-02' }, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + }); + + describe('querying without a groupBy parameter and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string = '', + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + test('rule tags are added in alert context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + const instanceID = '*'; + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); + + describe('querying with multiple criteria', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + thresholdA: number[], + thresholdB: number[], + groupBy: string = '', + sourceId: string = 'default' + ) => + executor({ + ...mockOptions, + services, + params: { + sourceId, + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold: thresholdA, + }, + { + ...baseNonCountCriterion, + comparator, + threshold: thresholdB, + metric: 'test.metric.2', + }, + ], + }, + }); + test('sends an alert when all criteria cross the threshold', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + const instanceID = '*'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + }); + test('sends no alert when some, but not all, criteria cross the threshold', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + {}, + ]); + const instanceID = '*'; + await execute(Comparator.LT_OR_EQ, [1.0], [2.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts only on groups that meet all criteria when querying with a groupBy parameter', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'something'); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('sends all criteria to the action context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + const instanceID = '*'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); + const { action } = mostRecentAction(instanceID); + const reasons = action.reason.split('\n'); + expect(reasons.length).toBe(2); + expect(reasons[0]).toContain('test.metric.1'); + expect(reasons[1]).toContain('test.metric.2'); + expect(reasons[0]).toContain('is 1'); + expect(reasons[1]).toContain('is 3'); + expect(reasons[0]).toContain('Alert when >= 1'); + expect(reasons[1]).toContain('Alert when >= 3'); + expect(reasons[0]).toContain('in the last 1 min'); + expect(reasons[1]).toContain('in the last 1 min'); + }); + }); + describe('querying with the count aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseCountCriterion, + comparator, + threshold, + } as CountMetricExpressionParams, + ], + }, + }); + test('alerts based on the doc_count value instead of the aggregatedValue', async () => { + setEvaluationResults([ + { + '*': { + ...baseCountCriterion, + comparator: Comparator.GT, + threshold: [0.9], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + }, + ]); + await execute(Comparator.GT, [0.9]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseCountCriterion, + comparator: Comparator.LT, + threshold: [0.5], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + }, + ]); + await execute(Comparator.LT, [0.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + describe('with a groupBy parameter', () => { + const executeGroupBy = ( + comparator: Comparator, + threshold: number[], + sourceId: string = 'default', + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + sourceId, + groupBy: 'something', + criteria: [ + { + ...baseCountCriterion, + comparator, + threshold, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + }); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + + test('successfully detects and alerts on a document count of 0', async () => { + setEvaluationResults([ + { + a: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const resultState = await executeGroupBy(Comparator.LT_OR_EQ, [0]); + expect(mostRecentAction(instanceIdA)).toBe(undefined); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + setEvaluationResults([ + { + a: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeGroupBy(Comparator.LT_OR_EQ, [0], 'empty-response', resultState); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + }); + }); + }); + describe('querying with the p99 aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + aggType: Aggregators.P99, + metric: 'test.metric.2', + }, + ], + }, + }); + test('alerts based on the p99 values', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.GT, [1]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.LT, [1]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + describe('querying with the p95 aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + aggType: Aggregators.P95, + metric: 'test.metric.1', + }, + ], + }, + }); + test('alerts based on the p95 values', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.25], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.GT, [0.25]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [0.95], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.LT, [0.95]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + describe("querying a metric that hasn't reported data", () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (alertOnNoData: boolean, sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.3', + }, + ], + alertOnNoData, + }, + }); + test('sends a No Data alert when configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(true); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action.reason).toEqual('test.metric.3 reported no data in the last 1m'); + expect(recentAction).toBeNoDataAction(); + }); + test('does not send a No Data alert when not configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(false); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + + describe('alerts with NO_DATA where one condtion is an aggregation and the other is a document count', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (alertOnNoData: boolean, sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.3', + }, + { + ...baseCountCriterion, + comparator: Comparator.GT, + threshold: [30], + }, + ], + alertOnNoData, + }, + }); + test('sends a No Data alert when configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + {}, + ]); + await execute(true); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action).toEqual({ + alertDetailsUrl: 'http://localhost:5601/app/observability/alerts/mock-alert-uuid', + alertState: 'NO DATA', + group: '*', + groupByKeys: undefined, + metric: { condition0: 'test.metric.3', condition1: 'count' }, + reason: 'test.metric.3 reported no data in the last 1m', + threshold: { condition0: ['1'], condition1: [30] }, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + value: { condition0: '[NO DATA]', condition1: 0 }, + viewInAppUrl: 'http://localhost:5601/app/metrics/explorer', + tags: [], + }); + expect(recentAction).toBeNoDataAction(); + }); + }); + + describe('querying a groupBy alert that starts reporting no data, and then later reports data', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const instanceIdA = 'a'; + const instanceIdB = 'b'; + const instanceIdC = 'c'; + const execute = (metric: string, alertOnGroupDisappear: boolean = true, state?: any) => + executor({ + ...mockOptions, + services, + params: { + groupBy: 'something', + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric, + }, + ], + alertOnNoData: true, + alertOnGroupDisappear, + }, + state: state ?? mockOptions.state.wrapped, + }); + + const executeEmptyResponse = (...args: [boolean?, any?]) => execute('test.metric.3', ...args); + const execute3GroupsABCResponse = (...args: [boolean?, any?]) => + execute('test.metric.2', ...args); + const execute2GroupsABResponse = (...args: [boolean?, any?]) => + execute('test.metric.1', ...args); + + // Store state between tests. Jest won't preserve reassigning a let so use an array instead. + const interTestStateStorage: any[] = []; + + test('first sends a No Data alert with the * group, but then reports groups when data is available', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + let resultState = await executeEmptyResponse(); + expect(mostRecentAction(instanceID)).toBeNoDataAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + resultState = await executeEmptyResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBeNoDataAction(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + resultState = await execute2GroupsABResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + interTestStateStorage.push(resultState); // Hand off resultState to the next test + }); + test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { + // Pop a previous execution result instead of defining it manually + // The type signature of alert executor states are complex + const resultState = interTestStateStorage.pop(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeEmptyResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); + expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); + }); + test('does not send individual No Data alerts when groups disappear if alertOnGroupDisappear is disabled', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const resultState = await execute3GroupsABCResponse(false); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + expect(mostRecentAction(instanceIdC)).toBeAlertAction(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute2GroupsABResponse(false, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + expect(mostRecentAction(instanceIdC)).toBe(undefined); + }); + + describe('if alertOnNoData is disabled but alertOnGroupDisappear is enabled', () => { + const executeWeirdNoDataConfig = (metric: string, state?: any) => + executor({ + ...mockOptions, + services, + params: { + groupBy: 'something', + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric, + }, + ], + alertOnNoData: false, + alertOnGroupDisappear: true, + }, + state: state ?? mockOptions.state.wrapped, + }); + + const executeWeirdEmptyResponse = (...args: [any?]) => + executeWeirdNoDataConfig('test.metric.3', ...args); + const executeWeird2GroupsABResponse = (...args: [any?]) => + executeWeirdNoDataConfig('test.metric.1', ...args); + + test('does not send a No Data alert with the * group, but then reports groups when data is available', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + let resultState = await executeWeirdEmptyResponse(); + expect(mostRecentAction(instanceID)).toBe(undefined); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + resultState = await executeWeirdEmptyResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + resultState = await executeWeird2GroupsABResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + interTestStateStorage.push(resultState); // Hand off resultState to the next test + }); + test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { + const resultState = interTestStateStorage.pop(); // Import the resultState from the previous test + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeWeirdEmptyResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); + expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); + }); + }); + }); + + describe('attempting to use a malformed filterQuery', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = () => + executor({ + ...mockOptions, + services, + params: { + criteria: [ + { + ...baseNonCountCriterion, + }, + ], + sourceId: 'default', + filterQuery: '', + filterQueryText: + 'host.name:(look.there.is.no.space.after.these.parentheses)and uh.oh: "wow that is bad"', + }, + }); + test('reports an error', async () => { + await execute(); + expect(mostRecentAction(instanceID)).toBeErrorAction(); + }); + }); + + describe('querying the entire infrastructure with warning threshold', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + + const execute = () => + executor({ + ...mockOptions, + services, + params: { + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [9999], + }, + ], + }, + }); + + const setResults = ({ + comparator = Comparator.GT, + threshold = [9999], + warningComparator = Comparator.GT, + warningThreshold = [2.49], + metric = 'test.metric.1', + currentValue = 7.59, + shouldWarn = false, + }) => + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator, + threshold, + warningComparator, + warningThreshold, + metric, + currentValue, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + test('warns as expected with the > comparator', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + expect(mostRecentAction(instanceID)).toBeWarnAction(); + + setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false }); + await execute(); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + + test('reports expected warning values to the action context', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe('test.metric.1 is 2.5 in the last 1 min. Alert when > 2.49.'); + }); + + test('reports expected warning values to the action context for percentage metric', async () => { + setResults({ + warningThreshold: [0.81], + currentValue: 0.82, + shouldWarn: true, + metric: 'system.cpu.user.pct', + }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe('system.cpu.user.pct is 82% in the last 1 min. Alert when > 81%.'); + }); + }); +}); + +const mockLibs: any = { + threshold_rule: { + group_by_page_size: 10000, + }, + basePath: { + publicBaseUrl: 'http://localhost:5601', + prepend: (path: string) => path, + }, + logger, +}; + +const executor = createMetricThresholdExecutor(mockLibs); + +const alertsServices = alertsMock.createRuleExecutorServices(); +const services: RuleExecutorServicesMock & + LifecycleAlertServices = { + ...alertsServices, + ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), +}; +services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => { + if (sourceId === 'alternate') + return { + id: 'alternate', + attributes: { metricAlias: 'alternatebeat-*' }, + type, + references: [], + }; + if (sourceId === 'empty-response') + return { + id: 'empty', + attributes: { metricAlias: 'empty-response' }, + type, + references: [], + }; + return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] }; +}); + +const alertInstances = new Map(); +services.alertFactory.create.mockImplementation((instanceID: string) => { + const newAlertInstance: AlertTestInstance = { + instance: alertsMock.createAlertFactory.create(), + actionQueue: [], + state: {}, + }; + const alertInstance: AlertTestInstance = persistAlertInstances + ? alertInstances.get(instanceID) || newAlertInstance + : newAlertInstance; + alertInstances.set(instanceID, alertInstance); + + alertInstance.instance.replaceState.mockImplementation((newState: any) => { + alertInstance.state = newState; + return alertInstance.instance; + }); + alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + }); + return alertInstance.instance; +}); + +function mostRecentAction(id: string) { + const instance = alertInstances.get(id); + if (!instance) return undefined; + return instance.actionQueue.pop(); +} + +function clearInstances() { + alertInstances.clear(); +} + +interface Action { + id: string; + action: { alertState: string }; +} + +expect.extend({ + toBeAlertAction(action?: Action) { + const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ALERT'; + const message = () => `expected ${action} to be an ALERT action`; + return { + message, + pass, + }; + }, + toBeWarnAction(action?: Action) { + const pass = action?.id === WARNING_ACTIONS.id && action?.action.alertState === 'WARNING'; + const message = () => `expected ${JSON.stringify(action)} to be an WARNING action`; + return { + message, + pass, + }; + }, + toBeNoDataAction(action?: Action) { + const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA'; + const message = () => `expected ${action} to be a NO DATA action`; + return { + message, + pass, + }; + }, + toBeErrorAction(action?: Action) { + const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ERROR'; + const message = () => `expected ${action} to be an ERROR action`; + return { + message, + pass, + }; + }, +}); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toBeAlertAction(action?: Action): R; + toBeWarnAction(action?: Action): R; + toBeNoDataAction(action?: Action): R; + toBeErrorAction(action?: Action): R; + } + } +} + +const baseNonCountCriterion = { + aggType: Aggregators.AVERAGE, + metric: 'test.metric.1', + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as NonCountMetricExpressionParams; + +const baseCountCriterion = { + aggType: Aggregators.COUNT, + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as CountMetricExpressionParams; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts new file mode 100644 index 0000000000000..084014c7c97c3 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts @@ -0,0 +1,499 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, ALERT_REASON } from '@kbn/rule-data-utils'; +import { isEqual } from 'lodash'; +import { + ActionGroupIdsOf, + AlertInstanceContext as AlertContext, + AlertInstanceState as AlertState, + RecoveredActionGroup, +} from '@kbn/alerting-plugin/common'; +import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; +import { IBasePath, Logger } from '@kbn/core/server'; +import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server'; +import { createFormatter } from '../../../../common/threshold_rule/formatters'; +import { Comparator } from '../../../../common/threshold_rule/types'; +import { ObservabilityConfig } from '../../..'; +import { TimeUnitChar } from '../../../../common/utils/formatters/duration'; +import { getOriginalActionGroup } from './utils'; +import { AlertStates } from './types'; + +import { + buildFiredAlertReason, + buildInvalidQueryAlertReason, + buildNoDataAlertReason, + // buildRecoveredAlertReason, + stateToAlertMessage, +} from './messages'; +import { + createScopedLogger, + AdditionalContext, + getAlertDetailsUrl, + getContextForRecoveredAlerts, + getViewInMetricsAppUrl, + UNGROUPED_FACTORY_KEY, + hasAdditionalContext, + validGroupByForContext, + flattenAdditionalContext, + getGroupByObject, +} from './utils'; + +import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; +import { MissingGroupsRecord } from './lib/check_missing_group'; +import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; + +export type MetricThresholdRuleParams = Record; +export type MetricThresholdRuleTypeState = RuleTypeState & { + lastRunTimestamp?: number; + missingGroups?: Array; + groupBy?: string | string[]; + filterQuery?: string; +}; +export type MetricThresholdAlertState = AlertState; // no specific instance state used +export type MetricThresholdAlertContext = AlertContext; // no specific instance state used + +export const FIRED_ACTIONS_ID = 'metrics.threshold.fired'; +export const WARNING_ACTIONS_ID = 'metrics.threshold.warning'; +export const NO_DATA_ACTIONS_ID = 'metrics.threshold.nodata'; + +type MetricThresholdActionGroup = + | typeof FIRED_ACTIONS_ID + | typeof WARNING_ACTIONS_ID + | typeof NO_DATA_ACTIONS_ID + | typeof RecoveredActionGroup.id; + +type MetricThresholdAllowedActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS | typeof NO_DATA_ACTIONS +>; + +type MetricThresholdAlert = Alert< + MetricThresholdAlertState, + MetricThresholdAlertContext, + MetricThresholdAllowedActionGroups +>; + +type MetricThresholdAlertFactory = ( + id: string, + reason: string, + actionGroup: MetricThresholdActionGroup, + additionalContext?: AdditionalContext | null, + evaluationValues?: Array +) => MetricThresholdAlert; + +export const createMetricThresholdExecutor = ({ + basePath, + logger, + config, +}: { + basePath: IBasePath; + logger: Logger; + config: ObservabilityConfig; +}): LifecycleRuleExecutor< + MetricThresholdRuleParams, + MetricThresholdRuleTypeState, + MetricThresholdAlertState, + MetricThresholdAlertContext, + MetricThresholdAllowedActionGroups +> => + async function (options) { + const startTime = Date.now(); + + const { + services, + params, + state, + startedAt, + executionId, + spaceId, + rule: { id: ruleId }, + } = options; + + const { criteria } = params; + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const thresholdLogger = createScopedLogger(logger, 'thresholdRule', { + alertId: ruleId, + executionId, + }); + + // TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340 + const { alertWithLifecycle, getAlertUuid, getAlertByAlertUuid, dataViews } = services; + + const alertFactory: MetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext, + evaluationValues + ) => + alertWithLifecycle({ + id, + fields: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + [ALERT_EVALUATION_VALUES]: evaluationValues, + ...flattenAdditionalContext(additionalContext), + }, + }); + // TODO: check if we need to use "sourceId" + const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as { + sourceId?: string; + alertOnNoData: boolean; + alertOnGroupDisappear: boolean | undefined; + }; + + if (!params.filterQuery && params.filterQueryText) { + try { + const { fromKueryExpression } = await import('@kbn/es-query'); + fromKueryExpression(params.filterQueryText); + } catch (e) { + thresholdLogger.error(e.message); + const timestamp = startedAt.toISOString(); + const actionGroupId = FIRED_ACTIONS_ID; // Change this to an Error action group when able + const reason = buildInvalidQueryAlertReason(params.filterQueryText); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + + alert.scheduleActions(actionGroupId, { + alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + reason, + timestamp, + value: null, + viewInAppUrl: getViewInMetricsAppUrl(basePath, spaceId), + }); + + return { + state: { + lastRunTimestamp: startedAt.valueOf(), + missingGroups: [], + groupBy: params.groupBy, + filterQuery: params.filterQuery, + }, + }; + } + } + + // For backwards-compatibility, interpret undefined alertOnGroupDisappear as true + const alertOnGroupDisappear = _alertOnGroupDisappear !== false; + const compositeSize = config.thresholdRule.groupByPageSize; + const filterQueryIsSame = isEqual(state.filterQuery, params.filterQuery); + const groupByIsSame = isEqual(state.groupBy, params.groupBy); + const previousMissingGroups = + alertOnGroupDisappear && filterQueryIsSame && groupByIsSame && state.missingGroups + ? state.missingGroups + : []; + // TODO: check the DATA VIEW + const defaultDataView = await dataViews.getDefaultDataView(); + const dataView = defaultDataView?.getIndexPattern(); + if (!dataView) { + throw new Error('No matched data view'); + } + + const alertResults = await evaluateRule( + services.scopedClusterClient.asCurrentUser, + params as EvaluatedRuleParams, + dataView, + compositeSize, + alertOnGroupDisappear, + logger, + state.lastRunTimestamp, + { end: startedAt.valueOf() }, + convertStringsToMissingGroupsRecord(previousMissingGroups) + ); + + const resultGroupSet = new Set(); + for (const resultSet of alertResults) { + for (const group of Object.keys(resultSet)) { + resultGroupSet.add(group); + } + } + + const groupByKeysObjectMapping = getGroupByObject(params.groupBy, resultGroupSet); + const groups = [...resultGroupSet]; + const nextMissingGroups = new Set(); + const hasGroups = !isEqual(groups, [UNGROUPED_FACTORY_KEY]); + let scheduledActionsCount = 0; + + // The key of `groups` is the alert instance ID. + for (const group of groups) { + // AND logic; all criteria must be across the threshold + const shouldAlertFire = alertResults.every((result) => result[group]?.shouldFire); + const shouldAlertWarn = alertResults.every((result) => result[group]?.shouldWarn); + // AND logic; because we need to evaluate all criteria, if one of them reports no data then the + // whole alert is in a No Data/Error state + const isNoData = alertResults.some((result) => result[group]?.isNoData); + + if (isNoData && group !== UNGROUPED_FACTORY_KEY) { + nextMissingGroups.add({ key: group, bucketKey: alertResults[0][group].bucketKey }); + } + + const nextState = isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING + : AlertStates.OK; + + let reason; + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { + reason = alertResults + .map((result) => + buildFiredAlertReason({ + ...formatAlertResult(result[group], nextState === AlertStates.WARNING), + group, + }) + ) + .join('\n'); + } + + /* NO DATA STATE HANDLING + * + * - `alertOnNoData` does not indicate IF the alert's next state is No Data, but whether or not the user WANTS TO BE ALERTED + * if the state were No Data. + * - `alertOnGroupDisappear`, on the other hand, determines whether or not it's possible to return a No Data state + * when a group disappears. + * + * This means we need to handle the possibility that `alertOnNoData` is false, but `alertOnGroupDisappear` is true + * + * nextState === NO_DATA would be true on both { '*': No Data } or, e.g. { 'a': No Data, 'b': OK, 'c': OK }, but if the user + * has for some reason disabled `alertOnNoData` and left `alertOnGroupDisappear` enabled, they would only care about the latter + * possibility. In this case, use hasGroups to determine whether to alert on a potential No Data state + * + * If `alertOnNoData` is true but `alertOnGroupDisappear` is false, we don't need to worry about the {a, b, c} possibility. + * At this point in the function, a false `alertOnGroupDisappear` would already have prevented group 'a' from being evaluated at all. + */ + if (alertOnNoData || (alertOnGroupDisappear && hasGroups)) { + // In the previous line we've determined if the user is interested in No Data states, so only now do we actually + // check to see if a No Data state has occurred + if (nextState === AlertStates.NO_DATA) { + reason = alertResults + .filter((result) => result[group]?.isNoData) + .map((result) => buildNoDataAlertReason({ ...result[group], group })) + .join('\n'); + } + } + + if (reason) { + const timestamp = startedAt.toISOString(); + const actionGroupId: MetricThresholdActionGroup = + nextState === AlertStates.OK + ? RecoveredActionGroup.id + : nextState === AlertStates.NO_DATA + ? NO_DATA_ACTIONS_ID + : nextState === AlertStates.WARNING + ? WARNING_ACTIONS_ID + : FIRED_ACTIONS_ID; + + const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) + ? alertResults && alertResults.length > 0 + ? alertResults[0][group].context ?? {} + : {} + : {}; + + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) + ); + + const evaluationValues = alertResults.reduce((acc: Array, result) => { + acc.push(result[group].currentValue); + return acc; + }, []); + + const alert = alertFactory( + `${group}`, + reason, + actionGroupId, + additionalContext, + evaluationValues + ); + const alertUuid = getAlertUuid(group); + scheduledActionsCount++; + + alert.scheduleActions(actionGroupId, { + alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[nextState], + group, + groupByKeys: groupByKeysObjectMapping[group], + metric: mapToConditionsLookup(criteria, (c) => { + if (c.aggType === 'count') { + return 'count'; + } + return c.metric; + }), + reason, + threshold: mapToConditionsLookup(alertResults, (result, index) => { + const evaluation = result[group]; + if (!evaluation) { + return criteria[index].threshold; + } + return formatAlertResult(evaluation).threshold; + }), + timestamp, + value: mapToConditionsLookup(alertResults, (result, index) => { + const evaluation = result[group]; + if (!evaluation && criteria[index].aggType === 'count') { + return 0; + } else if (!evaluation) { + return null; + } + return formatAlertResult(evaluation).currentValue; + }), + viewInAppUrl: getViewInMetricsAppUrl(basePath, spaceId), + ...additionalContext, + }); + } + } + + const { getRecoveredAlerts } = services.alertFactory.done(); + const recoveredAlerts = getRecoveredAlerts(); + + const groupByKeysObjectForRecovered = getGroupByObject( + params.groupBy, + new Set(recoveredAlerts.map((recoveredAlert) => recoveredAlert.getId())) + ); + + for (const alert of recoveredAlerts) { + const recoveredAlertId = alert.getId(); + const alertUuid = getAlertUuid(recoveredAlertId); + + const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; + const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); + + alert.setContext({ + alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, + groupByKeys: groupByKeysObjectForRecovered[recoveredAlertId], + metric: mapToConditionsLookup(criteria, (c) => { + if (criteria.aggType === 'count') { + return 'count'; + } + return c.metric; + }), + timestamp: startedAt.toISOString(), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + viewInAppUrl: getViewInMetricsAppUrl(basePath, spaceId), + + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS.id, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS.id, + // eslint-disable-next-line @typescript-eslint/naming-convention + originalAlertStateWasNO_DATA: originalActionGroup === NO_DATA_ACTIONS.id, + ...additionalContext, + }); + } + + const stopTime = Date.now(); + thresholdLogger.debug( + `Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms` + ); + return { + state: { + lastRunTimestamp: startedAt.valueOf(), + missingGroups: [...nextMissingGroups], + groupBy: params.groupBy, + filterQuery: params.filterQuery, + }, + }; + }; + +export const FIRED_ACTIONS = { + id: 'metrics.threshold.fired', + name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.fired', { + defaultMessage: 'Alert', + }), +}; + +export const WARNING_ACTIONS = { + id: 'metrics.threshold.warning', + name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.warning', { + defaultMessage: 'Warning', + }), +}; + +export const NO_DATA_ACTIONS = { + id: 'metrics.threshold.nodata', + name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.nodata', { + defaultMessage: 'No Data', + }), +}; + +const translateActionGroupToAlertState = ( + actionGroupId: string | undefined +): string | undefined => { + if (actionGroupId === FIRED_ACTIONS.id) { + return stateToAlertMessage[AlertStates.ALERT]; + } + if (actionGroupId === WARNING_ACTIONS.id) { + return stateToAlertMessage[AlertStates.WARNING]; + } + if (actionGroupId === NO_DATA_ACTIONS.id) { + return stateToAlertMessage[AlertStates.NO_DATA]; + } +}; + +const mapToConditionsLookup = ( + list: any[], + mapFn: (value: any, index: number, array: any[]) => unknown +) => + list.map(mapFn).reduce((result: Record, value, i) => { + result[`condition${i}`] = value; + return result; + }, {} as Record); + +const formatAlertResult = ( + alertResult: { + metric: string; + currentValue: number | null; + threshold: number[]; + comparator: Comparator; + warningThreshold?: number[]; + warningComparator?: Comparator; + timeSize: number; + timeUnit: TimeUnitChar; + } & AlertResult, + useWarningThreshold?: boolean +) => { + const { metric, currentValue, threshold, comparator, warningThreshold, warningComparator } = + alertResult; + const noDataValue = i18n.translate( + 'xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue', + { defaultMessage: '[NO DATA]' } + ); + const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold; + const comparatorToUse = useWarningThreshold ? warningComparator! : comparator; + + if (metric.endsWith('.pct')) { + const formatter = createFormatter('percent'); + return { + ...alertResult, + currentValue: + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + threshold: Array.isArray(thresholdToFormat) + ? thresholdToFormat.map((v: number) => formatter(v)) + : formatter(thresholdToFormat), + comparator: comparatorToUse, + }; + } + + const formatter = createFormatter('highPrecision'); + return { + ...alertResult, + currentValue: + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + threshold: Array.isArray(thresholdToFormat) + ? thresholdToFormat.map((v: number) => formatter(v)) + : formatter(thresholdToFormat), + comparator: comparatorToUse, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/threshold/types.ts new file mode 100644 index 0000000000000..10c276139ad68 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/types.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as rt from 'io-ts'; +import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; +import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { TimeUnitChar } from '../../../../common'; + +export enum InfraRuleType { + MetricThreshold = 'metrics.alert.threshold', + InventoryThreshold = 'metrics.alert.inventory.threshold', + Anomaly = 'metrics.alert.anomaly', +} + +export enum AlertStates { + OK, + ALERT, + WARNING, + NO_DATA, + ERROR, +} + +const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); +const metricAnomalyMetricRT = rt.union([ + rt.literal('memory_usage'), + rt.literal('network_in'), + rt.literal('network_out'), +]); +const metricAnomalyInfluencerFilterRT = rt.type({ + fieldName: rt.string, + fieldValue: rt.string, +}); + +export interface MetricAnomalyParams { + nodeType: rt.TypeOf; + metric: rt.TypeOf; + alertInterval?: string; + sourceId?: string; + spaceId?: string; + threshold: Exclude; + influencerFilter: rt.TypeOf | undefined; +} + +// Types for the executor + +interface BaseMetricExpressionParams { + timeSize: number; + timeUnit: TimeUnitChar; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + warningComparator?: Comparator; + warningThreshold?: number[]; +} + +export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Exclude; + metric: string; +} + +export interface CountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.COUNT; +} + +export type CustomMetricAggTypes = Exclude< + Aggregators, + Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 +>; + +export interface AlertExecutionDetails { + alertId: string; + executionId: string; +} diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts b/x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts new file mode 100644 index 0000000000000..808b75cdb36c9 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/utils.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 { flattenObject } from './utils'; + +describe('FlattenObject', () => { + it('flattens multi level item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + }, + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); + + it('does not flatten an array item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + item5: [1], + item6: { itemA: [1, 2, 3] }, + }, + key3: ['item7', 'item8'], + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + key3: ['item7', 'item8'], + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key2.item5': [1], + 'key2.item6.itemA': [1, 2, 3], + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); +}); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts b/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts new file mode 100644 index 0000000000000..99db849b8a54d --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts @@ -0,0 +1,324 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty, isError } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { Logger, LogMeta } from '@kbn/logging'; +import type { ElasticsearchClient, IBasePath } from '@kbn/core/server'; +import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { ES_FIELD_TYPES } from '@kbn/field-types'; +import { set } from '@kbn/safer-lodash-set'; +import { ParsedExperimentalFields } from '@kbn/rule-registry-plugin/common/parse_experimental_fields'; +import { ALERT_ACTION_GROUP } from '@kbn/rule-data-utils'; +import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import { ObservabilityConfig } from '../../..'; +import { AlertExecutionDetails } from './types'; + +const ALERT_CONTEXT_CONTAINER = 'container'; +const ALERT_CONTEXT_ORCHESTRATOR = 'orchestrator'; +const ALERT_CONTEXT_CLOUD = 'cloud'; +const ALERT_CONTEXT_HOST = 'host'; +const ALERT_CONTEXT_LABELS = 'labels'; +const ALERT_CONTEXT_TAGS = 'tags'; + +const HOST_NAME = 'host.name'; +const HOST_HOSTNAME = 'host.hostname'; +const HOST_ID = 'host.id'; +const CONTAINER_ID = 'container.id'; + +const SUPPORTED_ES_FIELD_TYPES = [ + ES_FIELD_TYPES.KEYWORD, + ES_FIELD_TYPES.IP, + ES_FIELD_TYPES.BOOLEAN, +]; + +export const oneOfLiterals = (arrayOfLiterals: Readonly) => + schema.string({ + validate: (value) => + arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`, + }); + +export const validateIsStringElasticsearchJSONFilter = (value: string) => { + if (value === '') { + // Allow clearing the filter. + return; + } + + const errorMessage = 'filterQuery must be a valid Elasticsearch filter expressed in JSON'; + try { + const parsedValue = JSON.parse(value); + if (!isEmpty(parsedValue.bool)) { + return undefined; + } + return errorMessage; + } catch (e) { + return errorMessage; + } +}; + +export const UNGROUPED_FACTORY_KEY = '*'; + +export const createScopedLogger = ( + logger: Logger, + scope: string, + alertExecutionDetails: AlertExecutionDetails +): Logger => { + const scopedLogger = logger.get(scope); + const fmtMsg = (msg: string) => + `[AlertId: ${alertExecutionDetails.alertId}][ExecutionId: ${alertExecutionDetails.executionId}] ${msg}`; + return { + ...scopedLogger, + info: (msg: string, meta?: Meta) => + scopedLogger.info(fmtMsg(msg), meta), + debug: (msg: string, meta?: Meta) => + scopedLogger.debug(fmtMsg(msg), meta), + trace: (msg: string, meta?: Meta) => + scopedLogger.trace(fmtMsg(msg), meta), + warn: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.warn(errorOrMessage, meta); + } else { + scopedLogger.warn(fmtMsg(errorOrMessage), meta); + } + }, + error: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.error(errorOrMessage, meta); + } else { + scopedLogger.error(fmtMsg(errorOrMessage), meta); + } + }, + fatal: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.fatal(errorOrMessage, meta); + } else { + scopedLogger.fatal(fmtMsg(errorOrMessage), meta); + } + }, + }; +}; + +export const getAlertDetailsPageEnabledForApp = ( + config: ObservabilityConfig['unsafe']['alertDetails'] | null, + appName: keyof ObservabilityConfig['unsafe']['alertDetails'] +): boolean => { + if (!config) return false; + + return config[appName].enabled; +}; + +export const getViewInMetricsAppUrl = (basePath: IBasePath, spaceId: string) => + addSpaceIdToPath(basePath.publicBaseUrl, spaceId, LINK_TO_METRICS_EXPLORER); + +export const getAlertDetailsUrl = ( + basePath: IBasePath, + spaceId: string, + alertUuid: string | null +) => addSpaceIdToPath(basePath.publicBaseUrl, spaceId, `/app/observability/alerts/${alertUuid}`); + +export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; +export const NUMBER_OF_DOCUMENTS = 10; +export const termsAggField: Record = { [KUBERNETES_POD_UID]: CONTAINER_ID }; + +export interface AdditionalContext { + [x: string]: any; +} + +export const doFieldsExist = async ( + esClient: ElasticsearchClient, + fields: string[], + index: string +): Promise> => { + // Get all supported fields + const respMapping = await esClient.fieldCaps({ + index, + fields: '*', + }); + + const fieldsExisted: Record = {}; + const acceptableFields: Set = new Set(); + + Object.entries(respMapping.fields).forEach(([key, value]) => { + const fieldTypes = Object.keys(value) as ES_FIELD_TYPES[]; + const isSupportedType = fieldTypes.some((type) => SUPPORTED_ES_FIELD_TYPES.includes(type)); + + // Check if fieldName is something we can aggregate on + if (isSupportedType) { + acceptableFields.add(key); + } + }); + + fields.forEach((field) => { + fieldsExisted[field] = acceptableFields.has(field); + }); + + return fieldsExisted; +}; + +export const validGroupByForContext: string[] = [ + HOST_NAME, + HOST_HOSTNAME, + HOST_ID, + KUBERNETES_POD_UID, + CONTAINER_ID, +]; + +export const hasAdditionalContext = ( + groupBy: string | string[] | undefined, + validGroups: string[] +): boolean => { + return groupBy + ? Array.isArray(groupBy) + ? groupBy.every((group) => validGroups.includes(group)) + : validGroups.includes(groupBy) + : false; +}; + +export const shouldTermsAggOnContainer = (groupBy: string | string[] | undefined) => { + return groupBy && Array.isArray(groupBy) + ? groupBy.includes(KUBERNETES_POD_UID) + : groupBy === KUBERNETES_POD_UID; +}; + +export const flattenAdditionalContext = ( + additionalContext: AdditionalContext | undefined | null +): AdditionalContext => { + return additionalContext ? flattenObject(additionalContext) : {}; +}; + +export const getContextForRecoveredAlerts = ( + alertHitSource: Partial | undefined | null +): AdditionalContext => { + const alert = alertHitSource ? unflattenObject(alertHitSource) : undefined; + + return { + cloud: alert?.[ALERT_CONTEXT_CLOUD], + host: alert?.[ALERT_CONTEXT_HOST], + orchestrator: alert?.[ALERT_CONTEXT_ORCHESTRATOR], + container: alert?.[ALERT_CONTEXT_CONTAINER], + labels: alert?.[ALERT_CONTEXT_LABELS], + tags: alert?.[ALERT_CONTEXT_TAGS], + }; +}; + +export const unflattenObject = (object: object): T => + Object.entries(object).reduce((acc, [key, value]) => { + set(acc, key, value); + return acc; + }, {} as T); + +export const flattenObject = (obj: AdditionalContext, prefix: string = ''): AdditionalContext => + Object.keys(obj).reduce((acc, key) => { + const nextValue = obj[key]; + + if (nextValue) { + if (typeof nextValue === 'object' && !Array.isArray(nextValue)) { + const dotSuffix = '.'; + if (Object.keys(nextValue).length > 0) { + return { + ...acc, + ...flattenObject(nextValue, `${prefix}${key}${dotSuffix}`), + }; + } + } + + const fullPath = `${prefix}${key}`; + acc[fullPath] = nextValue; + } + + return acc; + }, {}); + +export const getGroupByObject = ( + groupBy: string | string[] | undefined, + resultGroupSet: Set +): Record => { + const groupByKeysObjectMapping: Record = {}; + if (groupBy) { + resultGroupSet.forEach((groupSet) => { + const groupSetKeys = groupSet.split(','); + groupByKeysObjectMapping[groupSet] = unflattenObject( + Array.isArray(groupBy) + ? groupBy.reduce((result, group, index) => { + return { ...result, [group]: groupSetKeys[index]?.trim() }; + }, {}) + : { [groupBy]: groupSet } + ); + }); + } + return groupByKeysObjectMapping; +}; + +// TO BE MOVED +export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; + +export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; +export interface TooManyBucketsPreviewExceptionMetadata { + TOO_MANY_BUCKETS_PREVIEW_EXCEPTION: boolean; + maxBuckets: any; +} +export const isTooManyBucketsPreviewException = ( + value: any +): value is TooManyBucketsPreviewExceptionMetadata => + Boolean(value && value.TOO_MANY_BUCKETS_PREVIEW_EXCEPTION); + +export const LINK_TO_METRICS_EXPLORER = '/app/metrics/explorer'; + +export const getOriginalActionGroup = ( + alertHitSource: Partial | undefined | null +) => { + return alertHitSource?.[ALERT_ACTION_GROUP]; +}; + +const intervalUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']; +const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + intervalUnits.join('|') + ')$'); + +interface UnitsToSeconds { + [unit: string]: number; +} + +const units: UnitsToSeconds = { + ms: 0.001, + s: 1, + m: 60, + h: 3600, + d: 86400, + w: 86400 * 7, + M: 86400 * 30, + y: 86400 * 356, +}; + +export const getIntervalInSeconds = (interval: string): number => { + const matches = interval.match(INTERVAL_STRING_RE); + if (matches) { + return parseFloat(matches[1]) * units[matches[2]]; + } + throw new Error('Invalid interval string format.'); +}; + +export const calculateRateTimeranges = (timerange: { to: number; from: number }) => { + // This is the total number of milliseconds for the entire timerange + const totalTime = timerange.to - timerange.from; + // Halfway is the to minus half the total time; + const halfway = Math.round(timerange.to - totalTime / 2); + // The interval is half the total time (divided by 1000 to convert to seconds) + const intervalInSeconds = Math.round(totalTime / (2 * 1000)); + + // The first bucket is from the beginning of the time range to the halfway point + const firstBucketRange = { + from: timerange.from, + to: halfway, + }; + + // The second bucket is from the halfway point to the end of the timerange + const secondBucketRange = { + from: halfway, + to: timerange.to, + }; + + return { firstBucketRange, secondBucketRange, intervalInSeconds }; +}; diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index a0025eb72dd9a..f5bd1535b4bbc 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -15,9 +15,8 @@ import { } from '@kbn/core/server'; import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { PluginSetupContract, PluginStartContract } from '@kbn/alerting-plugin/server'; -import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; +import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; -import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; import { createUICapabilities as createCasesUICapabilities, getApiTags as getCasesApiTags, @@ -25,8 +24,6 @@ import { import { SharePluginSetup } from '@kbn/share-plugin/server'; import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; - -import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { kubernetesGuideId, @@ -41,15 +38,14 @@ import { import { uiSettings } from './ui_settings'; import { registerRoutes } from './routes/register_routes'; import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository'; -import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common'; import { compositeSlo, slo, SO_COMPOSITE_SLO_TYPE, SO_SLO_TYPE } from './saved_objects'; -import { SLO_RULE_REGISTRATION_CONTEXT } from './common/constants'; import { AlertsLocatorDefinition } from '../common/locators/alerts'; +import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common'; import { registerRuleTypes } from './lib/rules/register_rule_types'; -import { SLO_BURN_RATE_RULE_ID } from '../common/constants'; +import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants'; import { registerSloUsageCollector } from './lib/collectors/register'; -import { sloRuleFieldMap } from './lib/rules/slo_burn_rate/field_map'; import { OpenAIService } from './services/openai'; +import { threshold } from './saved_objects/threshold'; export type ObservabilityPluginSetup = ReturnType; @@ -186,7 +182,7 @@ export class ObservabilityPlugin implements Plugin { category: DEFAULT_APP_CATEGORIES.observability, app: [sloFeatureId, 'kibana'], catalogue: [sloFeatureId, 'observability'], - alerting: [SLO_BURN_RATE_RULE_ID], + alerting: [SLO_BURN_RATE_RULE_TYPE_ID], privileges: { all: { app: [sloFeatureId, 'kibana'], @@ -198,10 +194,10 @@ export class ObservabilityPlugin implements Plugin { }, alerting: { rule: { - all: [SLO_BURN_RATE_RULE_ID], + all: [SLO_BURN_RATE_RULE_TYPE_ID], }, alert: { - all: [SLO_BURN_RATE_RULE_ID], + all: [SLO_BURN_RATE_RULE_TYPE_ID], }, }, ui: ['read', 'write'], @@ -216,10 +212,10 @@ export class ObservabilityPlugin implements Plugin { }, alerting: { rule: { - read: [SLO_BURN_RATE_RULE_ID], + read: [SLO_BURN_RATE_RULE_TYPE_ID], }, alert: { - read: [SLO_BURN_RATE_RULE_ID], + read: [SLO_BURN_RATE_RULE_TYPE_ID], }, }, ui: ['read'], @@ -229,27 +225,14 @@ export class ObservabilityPlugin implements Plugin { core.savedObjects.registerType(slo); core.savedObjects.registerType(compositeSlo); + core.savedObjects.registerType(threshold); - const ruleDataClient = ruleDataService.initializeIndex({ - feature: sloFeatureId, - registrationContext: SLO_RULE_REGISTRATION_CONTEXT, - dataset: Dataset.alerts, - componentTemplateRefs: [], - componentTemplates: [ - { - name: 'mappings', - mappings: mappingFromFieldMap( - { ...legacyExperimentalFieldMap, ...sloRuleFieldMap }, - 'strict' - ), - }, - ], - }); registerRuleTypes( plugins.alerting, this.logger, - ruleDataClient, + ruleDataService, core.http.basePath, + config, alertsLocator ); registerSloUsageCollector(plugins.usageCollection); diff --git a/x-pack/plugins/observability/server/saved_objects/threshold.ts b/x-pack/plugins/observability/server/saved_objects/threshold.ts new file mode 100644 index 0000000000000..4bc55d308d1aa --- /dev/null +++ b/x-pack/plugins/observability/server/saved_objects/threshold.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 { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import type { SavedObject, SavedObjectsType } from '@kbn/core/server'; +import { metricsExplorerViewSavedObjectRT } from '../types'; + +export const thresholdExplorerViewSavedObjectName = 'threshold-explorer-view'; + +const getThresholdExplorerViewTitle = (savedObject: SavedObject) => + pipe( + metricsExplorerViewSavedObjectRT.decode(savedObject), + fold( + () => `Threshold explorer view [id=${savedObject.id}]`, + ({ attributes: { name } }) => name + ) + ); + +export const threshold: SavedObjectsType = { + name: thresholdExplorerViewSavedObjectName, + hidden: false, + namespaceType: 'multiple-isolated', + management: { + defaultSearchField: 'name', + displayName: 'threshold explorer view', + getTitle: getThresholdExplorerViewTitle, + icon: 'metricsApp', + importableAndExportable: true, + }, + mappings: { + dynamic: false, + properties: {}, + }, +}; diff --git a/x-pack/plugins/observability/server/types.ts b/x-pack/plugins/observability/server/types.ts index 15040bf07cedf..76a209f318078 100644 --- a/x-pack/plugins/observability/server/types.ts +++ b/x-pack/plugins/observability/server/types.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import * as rt from 'io-ts'; +import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import type { IRouter, CustomRequestHandlerContext, @@ -34,3 +35,21 @@ export type ObservabilityRequestHandlerContext = CustomRequestHandlerContext<{ * @internal */ export type ObservabilityPluginRouter = IRouter; + +export const metricsExplorerViewSavedObjectAttributesRT = rt.intersection([ + rt.strict({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, +]); + +export const metricsExplorerViewSavedObjectRT = rt.intersection([ + rt.type({ + id: rt.string, + attributes: metricsExplorerViewSavedObjectAttributesRT, + }), + rt.partial({ + version: rt.string, + updated_at: isoToEpochRt, + }), +]); diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 165ef25be3d8b..4adbf40cd1d94 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -69,7 +69,15 @@ "@kbn/observability-shared-plugin", "@kbn/exploratory-view-plugin", "@kbn/rison", + "@kbn/io-ts-utils", + "@kbn/ml-anomaly-utils", + "@kbn/observability-alert-details", + "@kbn/ui-actions-plugin", + "@kbn/field-types", + "@kbn/safer-lodash-set", "@kbn/core-http-server" ], - "exclude": ["target/**/*"] + "exclude": [ + "target/**/*" + ] } diff --git a/x-pack/plugins/observability_onboarding/public/assets/standalone_agent_setup.sh b/x-pack/plugins/observability_onboarding/public/assets/standalone_agent_setup.sh index 21d6375cdbf71..922bd614cc293 100644 --- a/x-pack/plugins/observability_onboarding/public/assets/standalone_agent_setup.sh +++ b/x-pack/plugins/observability_onboarding/public/assets/standalone_agent_setup.sh @@ -2,27 +2,121 @@ API_KEY_ENCODED=$1 API_ENDPOINT=$2 +ELASTIC_AGENT_VERSION=$3 +AUTO_DOWNLOAD_CONFIG=$4 updateStepProgress() { - echo " GET $API_ENDPOINT/step/$1?status=$2" + local STEPNAME="$1" + local STATUS="$2" # "incomplete" | "complete" | "disabled" | "loading" | "warning" | "danger" | "current" curl --request GET \ - --url "$API_ENDPOINT/step/$1?status=$2 2023-05-24" \ - --header "Authorization: ApiKey $API_KEY_ENCODED" \ + --url "${API_ENDPOINT}/custom_logs/step/${STEPNAME}?status=${STATUS}" \ + --header "Authorization: ApiKey ${API_KEY_ENCODED}" \ --header "Content-Type: application/json" \ - --header "kbn-xsrf: true" - echo "" + --header "kbn-xsrf: true" \ + --output /dev/null \ + --no-progress-meter } -echo "Downloading Elastic Agent" -# https://www.elastic.co/guide/en/fleet/8.7/install-standalone-elastic-agent.html -curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.7.1-linux-x86_64.tar.gz -updateStepProgress "ea-download" "success" +echo "Downloading Elastic Agent archive" +updateStepProgress "ea-download" "loading" +ELASTIC_AGENT_DOWNLOAD_URL="https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${ELASTIC_AGENT_VERSION}-linux-x86_64.tar.gz" +curl -L -O $ELASTIC_AGENT_DOWNLOAD_URL --fail --continue-at - +if [ "$?" -eq 0 ]; then + echo "Downloaded Elastic Agent" + updateStepProgress "ea-download" "complete" +else + updateStepProgress "ea-download" "danger" + echo "Failed to download Elastic Agent" + exit 1 +fi + echo "Extracting Elastic Agent" -tar xzvf elastic-agent-8.7.1-linux-x86_64.tar.gz -updateStepProgress "ea-extract" "success" +updateStepProgress "ea-extract" "loading" +tar -xzf elastic-agent-${ELASTIC_AGENT_VERSION}-linux-x86_64.tar.gz --checkpoint=.1000 +echo "" +if [ "$?" -eq 0 ]; then + echo "Elastic Agent extracted" + updateStepProgress "ea-extract" "complete" +else + updateStepProgress "ea-extract" "danger" + exit 1 +fi + echo "Installing Elastic Agent" -cd elastic-agent-8.7.1-linux-x86_64 +updateStepProgress "ea-install" "loading" +cd elastic-agent-${ELASTIC_AGENT_VERSION}-linux-x86_64 ./elastic-agent install -f -updateStepProgress "ea-install" "success" -echo "Sending status to Kibana..." -updateStepProgress "ea-status" "active" +if [ "$?" -eq 0 ]; then + echo "Elastic Agent installed" + updateStepProgress "ea-install" "complete" +else + updateStepProgress "ea-install" "danger" + exit 1 +fi + +waitForElasticAgentStatus() { + local MAX_RETRIES=10 + local i=0 + echo -n "." + elastic-agent status >/dev/null + local ELASTIC_AGENT_STATUS_EXIT_CODE="$?" + while [ "$ELASTIC_AGENT_STATUS_EXIT_CODE" -ne 0 ] && [ $i -le $MAX_RETRIES ]; do + sleep 1 + echo -n "." + elastic-agent status >/dev/null + ELASTIC_AGENT_STATUS_EXIT_CODE="$?" + ((i++)) + done + echo "" + return $ELASTIC_AGENT_STATUS_EXIT_CODE +} + +echo "Checking Elastic Agent status" +updateStepProgress "ea-status" "loading" +waitForElasticAgentStatus +if [[ "$?" -ne 0 ]]; then + updateStepProgress "ea-status" "warning" + exit 1 +fi +ELASTIC_AGENT_STATE="$(elastic-agent status | grep -m1 State | sed 's/State: //')" +ELASTIC_AGENT_MESSAGE="$(elastic-agent status | grep -m1 Message | sed 's/Message: //')" +if [ "${ELASTIC_AGENT_STATE}" = "HEALTHY" ] && [ "${ELASTIC_AGENT_MESSAGE}" = "Running" ]; then + echo "Elastic Agent running" + echo "Download and save configuration to /opt/Elastic/Agent/elastic-agent.yml" + updateStepProgress "ea-status" "complete" +else + updateStepProgress "ea-status" "warning" + exit 1 +fi + +downloadElasticAgentConfig() { + echo "Downloading elastic-agent.yml" + updateStepProgress "ea-config" "loading" + curl --request GET \ + --url "${API_ENDPOINT}/elastic_agent/config" \ + --header "Authorization: ApiKey ${API_KEY_ENCODED}" \ + --header "Content-Type: application/json" \ + --header "kbn-xsrf: true" \ + --no-progress-meter \ + --output /opt/Elastic/Agent/elastic-agent.yml + + if [ "$?" -eq 0 ]; then + echo "Downloaded elastic-agent.yml" + updateStepProgress "ea-config" "complete" + else + updateStepProgress "ea-config" "warning" + echo "Failed to download elastic-agent.yml" + exit 1 + fi + +} + +if [[ "${AUTO_DOWNLOAD_CONFIG}" == *"autoDownloadConfig=1"* ]]; then + downloadElasticAgentConfig + echo "Done with standalone Elastic Agent setup for custom logs. Look for streaming logs to arrive in Kibana." +else + echo "Done with standalone Elastic Agent setup for custom logs. Make sure to add your configuration to /opt/Elastic/Agent/elastic-agent.yml, then look for streaming logs to arrive in Kibana." +fi + +echo "Exit" +exit 0 diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx new file mode 100644 index 0000000000000..f7e73161fa8b3 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonIcon, + EuiCallOut, + EuiCopy, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { i18n } from '@kbn/i18n'; +import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { APIReturnType } from '../../../../services/rest/create_call_api'; + +type ApiKeyPayload = + APIReturnType<'POST /internal/observability_onboarding/custom_logs/save'>; + +export type HasPrivileges = boolean; + +export function ApiKeyBanner({ + hasPrivileges = true, + status, + payload, + error, +}: { + hasPrivileges?: boolean; + status: FETCH_STATUS; + payload?: ApiKeyPayload; + error?: IHttpFetchError; +}) { + const loadingCallout = ( + + + + + + {i18n.translate( + 'xpack.observability_onboarding.apiKeyBanner.loading', + { + defaultMessage: 'Creating API Key', + } + )} + + + } + color="primary" + /> + ); + + const apiKeySuccessCallout = ( + +

    + {i18n.translate( + 'xpack.observability_onboarding.apiKeyBanner.created.description', + { + defaultMessage: + 'Remember to store this information in a safe place. It won’t be displayed anymore after you continue.', + } + )} +

    + + {(copy) => ( + + )} + + } + /> +
    + ); + + const apiKeyFailureCallout = ( + +

    + {i18n.translate( + 'xpack.observability_onboarding.apiKeyBanner.failed.description', + { + defaultMessage: 'Something went wrong: {message}', + values: { + message: error?.body?.message, + }, + } + )} +

    +
    + ); + + const noPermissionsCallout = ( + +

    + {i18n.translate( + 'xpack.observability_onboarding.apiKeyBanner.noPermissions.description', + { + defaultMessage: + 'Required cluster privileges are {requiredClusterPrivileges} and required index privileges are {requiredIndexPrivileges} for indices {indices}, please add all required privileges to the role of the authenticated user.', + values: { + requiredClusterPrivileges: "['monitor', 'manage_own_api_key']", + requiredIndexPrivileges: "['auto_configure', 'create_doc']", + indices: "['logs-*-*', 'metrics-*-*']", + }, + } + )} +

    +
    + ); + + if (!hasPrivileges) { + return noPermissionsCallout; + } + + if (status === FETCH_STATUS.SUCCESS) { + return apiKeySuccessCallout; + } + + if (status === FETCH_STATUS.FAILURE) { + return apiKeyFailureCallout; + } + + return loadingCallout; +} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/collect_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/collect_logs.tsx deleted file mode 100644 index 0375d82678f05..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/collect_logs.tsx +++ /dev/null @@ -1,109 +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 { - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiLoadingSpinner, - EuiSpacer, - EuiSteps, - EuiText, -} from '@elastic/eui'; -import React from 'react'; -import { useWizard } from '.'; -import { useFetcher } from '../../../../hooks/use_fetcher'; -import { - StepPanel, - StepPanelContent, - StepPanelFooter, -} from '../../../shared/step_panel'; - -export function CollectLogs() { - const { goToStep, goBack } = useWizard(); - - const { data } = useFetcher((callApi) => { - return callApi('GET /internal/observability_onboarding/get_status'); - }, []); - - function onContinue() { - goToStep('inspect'); - } - - function onBack() { - goBack(); - } - - return ( - - Back - , - - Continue - , - ]} - /> - } - > - - -

    - It might take a few minutes for the data to get to Elasticsearch. If - you're not seeing any, try generating some to verify. If - you're having trouble connecting, check out the troubleshooting - guide. -

    -
    - - - - - - - - -

    Listening for incoming logs

    -
    -
    -
    -
    - - - - - - Need some help? - - -
    -
    - ); -} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx index ac2636dd95511..689068ba792dc 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx @@ -98,7 +98,7 @@ export function ConfigureLogs() { panelFooter={ + {i18n.translate('xpack.observability_onboarding.steps.back', { defaultMessage: 'Back', })} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/horizontal_steps.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/horizontal_steps.tsx index cfbeacec438cd..6969c20f12ced 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/horizontal_steps.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/horizontal_steps.tsx @@ -69,19 +69,6 @@ export function HorizontalSteps() { goToStep('installElasticAgent'); }, }, - { - title: i18n.translate( - 'xpack.observability_onboarding.steps.collectLogs', - { - defaultMessage: 'Collect logs', - } - ), - status: getStatus('collectLogs'), - disabled: isDisabled('collectLogs'), - onClick: () => { - goToStep('collectLogs'); - }, - }, ]} /> ); diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/import_data.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/import_data.tsx deleted file mode 100644 index 0375d82678f05..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/import_data.tsx +++ /dev/null @@ -1,109 +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 { - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiLoadingSpinner, - EuiSpacer, - EuiSteps, - EuiText, -} from '@elastic/eui'; -import React from 'react'; -import { useWizard } from '.'; -import { useFetcher } from '../../../../hooks/use_fetcher'; -import { - StepPanel, - StepPanelContent, - StepPanelFooter, -} from '../../../shared/step_panel'; - -export function CollectLogs() { - const { goToStep, goBack } = useWizard(); - - const { data } = useFetcher((callApi) => { - return callApi('GET /internal/observability_onboarding/get_status'); - }, []); - - function onContinue() { - goToStep('inspect'); - } - - function onBack() { - goBack(); - } - - return ( - - Back - , - - Continue - , - ]} - /> - } - > - - -

    - It might take a few minutes for the data to get to Elasticsearch. If - you're not seeing any, try generating some to verify. If - you're having trouble connecting, check out the troubleshooting - guide. -

    -
    - - - - - - - - -

    Listening for incoming logs

    -
    -
    -
    -
    - - - - - - Need some help? - - -
    - - ); -} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx index eb3a7bb395ff1..9f1950027015e 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx @@ -9,7 +9,6 @@ import { ConfigureLogs } from './configure_logs'; import { SelectLogs } from './select_logs'; import { InstallElasticAgent } from './install_elastic_agent'; import { createWizardContext } from '../../../../context/create_wizard_context'; -import { CollectLogs } from './collect_logs'; import { Inspect } from './inspect'; interface WizardState { @@ -27,12 +26,7 @@ interface WizardState { | 'service'; uploadType?: 'log-file' | 'api-key'; elasticAgentPlatform: 'linux-tar' | 'macos' | 'windows' | 'deb' | 'rpm'; - alternativeShippers: { - filebeat: boolean; - fluentbit: boolean; - logstash: boolean; - fluentd: boolean; - }; + autoDownloadConfig: boolean; } const initialState: WizardState = { @@ -41,12 +35,7 @@ const initialState: WizardState = { namespace: 'default', customConfigurations: '', elasticAgentPlatform: 'linux-tar', - alternativeShippers: { - filebeat: false, - fluentbit: false, - logstash: false, - fluentd: false, - }, + autoDownloadConfig: false, }; const { Provider, Step, useWizard } = createWizardContext({ @@ -56,7 +45,6 @@ const { Provider, Step, useWizard } = createWizardContext({ selectLogs: SelectLogs, configureLogs: ConfigureLogs, installElasticAgent: InstallElasticAgent, - collectLogs: CollectLogs, inspect: Inspect, }, }); diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx index 3209378258948..72f05c65c7564 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx @@ -22,7 +22,7 @@ export function Inspect() { panelFooter={ + Back , ]} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx index 6490b951fae76..8c12771c9c560 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx @@ -5,46 +5,93 @@ * 2.0. */ -import { Buffer } from 'buffer'; -import { flatten, zip } from 'lodash'; -import React, { useState } from 'react'; import { - EuiText, EuiButton, - EuiSpacer, + EuiButtonEmpty, EuiButtonGroup, EuiCodeBlock, - EuiSteps, + EuiFlexGroup, + EuiFlexItem, EuiSkeletonRectangle, + EuiSpacer, + EuiStep, + EuiSteps, + EuiStepsProps, + EuiSubSteps, + EuiSwitch, + EuiText, } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { Buffer } from 'buffer'; +import { flatten, zip } from 'lodash'; +import { default as React, useCallback, useEffect, useState } from 'react'; +import { useWizard } from '.'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useKibanaNavigation } from '../../../../hooks/use_kibana_navigation'; import { StepPanel, StepPanelContent, StepPanelFooter, } from '../../../shared/step_panel'; -import { useWizard } from '.'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { ApiKeyBanner } from './api_key_banner'; type ElasticAgentPlatform = 'linux-tar' | 'macos' | 'windows'; export function InstallElasticAgent() { - const { goToStep, goBack, getState, CurrentStep } = useWizard(); + const { navigateToKibanaUrl } = useKibanaNavigation(); + const { goBack, goToStep, getState, setState, CurrentStep } = useWizard(); const wizardState = getState(); const [elasticAgentPlatform, setElasticAgentPlatform] = useState('linux-tar'); + const [apiKeyId, setApiKeyId] = useState(''); + function onInspect() { + goToStep('inspect'); + } function onContinue() { - goToStep('collectLogs'); + navigateToKibanaUrl('/app/logs/stream'); } function onBack() { goBack(); } - const { data: installShipperSetup, status: installShipperSetupStatus } = - useFetcher((callApi) => { + function onAutoDownloadConfig() { + const { autoDownloadConfig, ...state } = getState(); + setState({ ...state, autoDownloadConfig: !autoDownloadConfig }); + } + + const { data: monitoringRole, status: monitoringRoleStatus } = useFetcher( + (callApi) => { if (CurrentStep === InstallElasticAgent) { return callApi( - 'POST /internal/observability_onboarding/custom_logs/install_shipper_setup', + 'GET /internal/observability_onboarding/custom_logs/privileges' + ); + } + }, + [] + ); + + const { data: setup } = useFetcher((callApi) => { + if (CurrentStep === InstallElasticAgent) { + return callApi( + 'GET /internal/observability_onboarding/custom_logs/install_shipper_setup' + ); + } + }, []); + + const { + data: installShipperSetup, + status: installShipperSetupStatus, + error, + } = useFetcher( + (callApi) => { + if ( + CurrentStep === InstallElasticAgent && + monitoringRole?.hasPrivileges + ) { + return callApi( + 'POST /internal/observability_onboarding/custom_logs/save', { params: { body: { @@ -60,65 +107,234 @@ export function InstallElasticAgent() { } ); } - }, []); + }, + [monitoringRole?.hasPrivileges] + ); const { data: yamlConfig = '', status: yamlConfigStatus } = useFetcher( (callApi) => { - if (CurrentStep === InstallElasticAgent && installShipperSetup) { + if (CurrentStep === InstallElasticAgent) { + const options = { + headers: { + authorization: `ApiKey ${installShipperSetup?.apiKeyEncoded}`, + }, + }; + return callApi( 'GET /api/observability_onboarding/elastic_agent/config 2023-05-24', + installShipperSetup?.apiKeyEncoded ? options : {} + ); + } + }, + [installShipperSetup?.apiKeyEncoded] + ); + + useEffect(() => { + setApiKeyId(installShipperSetup?.apiKeyId ?? ''); + }, [installShipperSetup?.apiKeyId]); + + const apiKeyEncoded = installShipperSetup?.apiKeyEncoded; + + const { + data: progressData, + status: progressStatus, + refetch: refetchProgress, + } = useFetcher( + (callApi) => { + if (CurrentStep === InstallElasticAgent && apiKeyId) { + return callApi( + 'GET /internal/observability_onboarding/custom_logs/progress', { - headers: { - authorization: `ApiKey ${installShipperSetup.apiKeyEncoded}`, - }, + params: { query: { apiKeyId } }, } ); } }, - [installShipperSetup?.apiKeyId, installShipperSetup?.apiKeyEncoded] + [apiKeyId] ); - const apiKeyEncoded = installShipperSetup?.apiKeyEncoded; + const progressSucceded = progressStatus === FETCH_STATUS.SUCCESS; + + useEffect(() => { + if (progressSucceded) { + setTimeout(() => { + refetchProgress(); + }, 2000); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [progressSucceded]); + + const getStep = useCallback( + ({ id, incompleteTitle, loadingTitle, completedTitle }) => { + const progress = progressData?.progress; + if (progress) { + const stepStatus = progress[ + id + ] as EuiStepsProps['steps'][number]['status']; + const title = + stepStatus === 'loading' + ? loadingTitle + : stepStatus === 'complete' + ? completedTitle + : incompleteTitle; + return { + title, + children: null, + status: stepStatus ?? ('incomplete' as const), + }; + } + return { + title: incompleteTitle, + children: null, + status: 'incomplete' as const, + }; + }, + [progressData?.progress] + ); + + const isInstallStarted = progressData?.progress['ea-download'] !== undefined; + const isInstallCompleted = progressData?.progress['ea-status'] === 'complete'; + + const autoDownloadConfigStep = getStep({ + id: 'ea-config', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle', + { defaultMessage: 'Configure the agent' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaConfig.loadingTitle', + { defaultMessage: 'Downloading Elastic Agent config' } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle', + { + defaultMessage: 'Elastic Agent config written to {configPath}', + values: { configPath: '/opt/Elastic/Agent/elastic-agent.yml' }, + } + ), + }); return ( - + + {i18n.translate('xpack.observability_onboarding.steps.back', { + defaultMessage: 'Back', + })} + , + + + + {i18n.translate( + 'xpack.observability_onboarding.steps.inspect', + { defaultMessage: 'Inspect' } + )} + + + + + {i18n.translate( + 'xpack.observability_onboarding.steps.exploreLogs', + { defaultMessage: 'Explore logs' } + )} + + + , + ]} + /> + } + >

    - Add Elastic Agent to your hosts to begin sending data to your - Elastic Cloud. Run standalone if you want to download and manage - each agent configuration file on your own, or enroll in Fleet, for - centralized management of all your agents through our Fleet managed - interface. + {i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.description', + { + defaultMessage: + 'Add Elastic Agent to your hosts to begin sending data to your Elastic Cloud. Run standalone if you want to download and manage each agent configuration file on your own, or enroll in Fleet, for centralized management of all your agents through our Fleet managed interface.', + } + )}

    + {monitoringRoleStatus !== FETCH_STATUS.NOT_INITIATED && + monitoringRoleStatus !== FETCH_STATUS.LOADING && ( + + )} +

    - Select a platform and run the command to install in your - Terminal, enroll, and start the Elastic Agent. Do this for - each host. For other platforms, see our downloads page. - Review host requirements and other installation options. + {i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.installStep.description', + { + defaultMessage: + 'Select a platform and run the command to install in your Terminal, enroll, and start the Elastic Agent. Do this for each host. For other platforms, see our downloads page. Review host requirements and other installation options.', + } + )}

    - + {getInstallShipperCommand({ + elasticAgentPlatform, + apiKeyEncoded, + apiEndpoint: setup?.apiEndpoint, + scriptDownloadUrl: setup?.scriptDownloadUrl, + elasticAgentVersion: setup?.elasticAgentVersion, + autoDownloadConfig: wizardState.autoDownloadConfig, + })} + + + - - {getInstallShipperCommand({ - elasticAgentPlatform, - apiKeyEncoded, - apiEndpoint: installShipperSetup?.apiEndpoint, - scriptDownloadUrl: - installShipperSetup?.scriptDownloadUrl, - })} - - + /> + {isInstallStarted && ( + <> + + + {[ + { + id: 'ea-download', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaDownload.incompleteTitle', + { defaultMessage: 'Download Elastic Agent' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaDownload.loadingTitle', + { defaultMessage: 'Downloading Elastic Agent' } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaDownload.completedTitle', + { defaultMessage: 'Elastic Agent downloaded' } + ), + }, + { + id: 'ea-extract', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaExtract.incompleteTitle', + { defaultMessage: 'Extract Elastic Agent' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaExtract.loadingTitle', + { defaultMessage: 'Extracting Elastic Agent' } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaExtract.completedTitle', + { defaultMessage: 'Elastic Agent extracted' } + ), + }, + { + id: 'ea-install', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaInstall.incompleteTitle', + { defaultMessage: 'Install Elastic Agent' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaInstall.loadingTitle', + { defaultMessage: 'Installing Elastic Agent' } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaInstall.completedTitle', + { defaultMessage: 'Elastic Agent installed' } + ), + }, + { + id: 'ea-status', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaStatus.incompleteTitle', + { defaultMessage: 'Connect to the Elastic Agent' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaStatus.loadingTitle', + { + defaultMessage: + 'Connecting to the Elastic Agent', + } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaStatus.completedTitle', + { + defaultMessage: + 'Connected to the Elastic Agent', + } + ), + }, + ].map((step, index) => { + const { title, status } = getStep(step); + return ( + .euiStep__content': { + paddingBottom: 0, + }, + })} + /> + ); + })} + + + )} ), }, { - title: 'Configure the agent', + title: wizardState.autoDownloadConfig + ? autoDownloadConfigStep.title + : i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle', + { defaultMessage: 'Configure the agent' } + ), status: yamlConfigStatus === FETCH_STATUS.LOADING ? 'loading' - : 'incomplete', + : autoDownloadConfigStep.status, children: ( <>

    - Copy the config below to the elastic agent.yml on the host - where the Elastic Agent is installed. + {wizardState.autoDownloadConfig + ? i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.configStep.auto.description', + { + defaultMessage: + 'The agent config below will be downloaded by the install script and written to ({configPath}). This will overwrite any existing agent configuration.', + values: { + configPath: + '/opt/Elastic/Agent/elastic-agent.yml', + }, + } + ) + : i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.configStep.manual.description', + { + defaultMessage: + 'Copy the config below to the elastic agent.yml on the host where the Elastic Agent is installed ({configPath}).', + values: { + configPath: + '/opt/Elastic/Agent/elastic-agent.yml', + }, + } + )}

    - + {yamlConfig} @@ -185,25 +531,34 @@ export function InstallElasticAgent() { ).toString('base64')}`} download="elastic-agent.yml" target="_blank" + isDisabled={wizardState.autoDownloadConfig} > - Download config file + {i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.configStep.downloadConfigButton', + { defaultMessage: 'Download config file' } + )} ), }, + getStep({ + id: 'logs-ingest', + incompleteTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.incompleteTitle', + { defaultMessage: 'Check for shipped logs' } + ), + loadingTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle', + { defaultMessage: 'Waiting for logs to be shipped' } + ), + completedTitle: i18n.translate( + 'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle', + { defaultMessage: 'Logs are being shipped!' } + ), + }), ]} />
    - - Back - , - - Continue - , - ]} - />
    ); } @@ -213,17 +568,23 @@ function getInstallShipperCommand({ apiKeyEncoded = '$API_KEY', apiEndpoint = '$API_ENDPOINT', scriptDownloadUrl = '$SCRIPT_DOWNLOAD_URL', + elasticAgentVersion = '$ELASTIC_AGENT_VERSION', + autoDownloadConfig = false, }: { elasticAgentPlatform: ElasticAgentPlatform; apiKeyEncoded: string | undefined; apiEndpoint: string | undefined; scriptDownloadUrl: string | undefined; + elasticAgentVersion: string | undefined; + autoDownloadConfig: boolean; }) { const setupScriptFilename = 'standalone_agent_setup.sh'; const PLATFORM_COMMAND: Record = { 'linux-tar': oneLine` curl ${scriptDownloadUrl} -o ${setupScriptFilename} && - sudo bash ${setupScriptFilename} ${apiKeyEncoded} ${apiEndpoint} + sudo bash ${setupScriptFilename} ${apiKeyEncoded} ${apiEndpoint} ${elasticAgentVersion} ${ + autoDownloadConfig ? 'autoDownloadConfig=1' : '' + } `, macos: oneLine` curl -O https://elastic.co/agent-setup.sh && diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/name_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/name_logs.tsx deleted file mode 100644 index 2f1b56d8491e5..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/name_logs.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { - EuiText, - EuiButton, - EuiButtonEmpty, - EuiForm, - EuiFormRow, - EuiFieldText, - EuiSpacer, -} from '@elastic/eui'; -import { - StepPanel, - StepPanelContent, - StepPanelFooter, -} from '../../../shared/step_panel'; -import { useWizard } from '.'; - -export function NameLogs() { - const { goToStep, getState, setState } = useWizard(); - const wizardState = getState(); - const [datasetName, setDatasetName] = useState(wizardState.datasetName); - - function onContinue() { - setState({ ...getState(), datasetName }); - goToStep('configureLogs'); - } - - return ( - - Skip for now - , - - Save and Continue - , - ]} - /> - } - > - - -

    Pick a name for your logs, this will become your dataset name.

    -
    - - - - setDatasetName(event.target.value)} - /> - - -
    -
    - ); -} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx index febedae27f26b..6d09577fedc57 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx @@ -48,7 +48,7 @@ export function SelectLogs() { panelFooter={ + {i18n.translate('xpack.observability_onboarding.steps.back', { defaultMessage: 'Back', })} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx index 544e675724964..da2441a1d8b7a 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx @@ -55,7 +55,7 @@ export function Home() { > - +

    {i18n.translate('xpack.observability_onboarding.home.title', { defaultMessage: 'Get started with Observability', @@ -287,7 +287,10 @@ export function Home() { - + {i18n.translate('xpack.observability_onboarding.skipLinkLabel', { defaultMessage: 'Skip for now', })} diff --git a/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts b/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts index e8c39fa783dd2..6f484a0e8ed20 100644 --- a/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts +++ b/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts @@ -6,10 +6,16 @@ */ import { CoreSetup, CoreStart } from '@kbn/core/public'; -import type { RouteRepositoryClient } from '@kbn/server-route-repository'; +import type { + ReturnOf, + RouteRepositoryClient, +} from '@kbn/server-route-repository'; import { formatRequest } from '@kbn/server-route-repository'; import { FetchOptions } from '../../../common/fetch_options'; -import type { ObservabilityOnboardingServerRouteRepository } from '../../../server/routes'; +import type { + APIEndpoint, + ObservabilityOnboardingServerRouteRepository, +} from '../../../server/routes'; import { CallApi, callApi } from './call_api'; export type ObservabilityOnboardingClientOptions = Omit< @@ -29,6 +35,11 @@ export type AutoAbortedObservabilityClient = RouteRepositoryClient< Omit >; +export type APIReturnType = ReturnOf< + ObservabilityOnboardingServerRouteRepository, + TEndpoint +>; + export let callObservabilityOnboardingApi: ObservabilityOnboardingClient = () => { throw new Error( diff --git a/x-pack/plugins/observability_onboarding/server/lib/get_authentication_api_key.ts b/x-pack/plugins/observability_onboarding/server/lib/get_authentication_api_key.ts index 6353487bf70df..e9b7f4aadef61 100644 --- a/x-pack/plugins/observability_onboarding/server/lib/get_authentication_api_key.ts +++ b/x-pack/plugins/observability_onboarding/server/lib/get_authentication_api_key.ts @@ -10,6 +10,16 @@ import { HTTPAuthorizationHeader } from '@kbn/security-plugin/server'; export const getAuthenticationAPIKey = (request: KibanaRequest) => { const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); + if (!authorizationHeader) { + throw new Error('Authorization header is missing'); + } + + // If we don't perform this check we could end up exposing user password because this will return + // something similar to username:password when we are using basic authentication + if (authorizationHeader.scheme.toLowerCase() !== 'apikey') { + return null; + } + if (authorizationHeader && authorizationHeader.credentials) { const apiKey = Buffer.from(authorizationHeader.credentials, 'base64') .toString() @@ -19,5 +29,4 @@ export const getAuthenticationAPIKey = (request: KibanaRequest) => { apiKey: apiKey[1], }; } - throw new Error('Authorization header is missing'); }; diff --git a/x-pack/plugins/observability_onboarding/server/plugin.ts b/x-pack/plugins/observability_onboarding/server/plugin.ts index c46e4d24d9fe9..e0e64948359e9 100644 --- a/x-pack/plugins/observability_onboarding/server/plugin.ts +++ b/x-pack/plugins/observability_onboarding/server/plugin.ts @@ -63,11 +63,13 @@ export class ObservabilityOnboardingPlugin }; }) as ObservabilityOnboardingRouteHandlerResources['plugins']; + const config = this.initContext.config.get(); registerRoutes({ core, logger: this.logger, repository: getObservabilityOnboardingServerRouteRepository(), plugins: resourcePlugins, + config, }); return {}; diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/create_shipper_api_key.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/create_shipper_api_key.ts similarity index 78% rename from x-pack/plugins/observability_onboarding/server/routes/custom_logs/create_shipper_api_key.ts rename to x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/create_shipper_api_key.ts index 0648d68be35c3..6ad4a60cccc62 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/create_shipper_api_key.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/create_shipper_api_key.ts @@ -6,6 +6,7 @@ */ import { ElasticsearchClient } from '@kbn/core/server'; +import { cluster, indices } from './monitoring_config'; export function createShipperApiKey( esClient: ElasticsearchClient, @@ -18,13 +19,8 @@ export function createShipperApiKey( metadata: { application: 'logs' }, role_descriptors: { standalone_agent: { - cluster: ['monitor'], - indices: [ - { - names: ['logs-*-*', 'metrics-*-*'], - privileges: ['auto_configure', 'create_doc'], - }, - ], + cluster, + indices, }, }, }, diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/has_log_monitoring_privileges.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/has_log_monitoring_privileges.ts new file mode 100644 index 0000000000000..2446f0fa04af7 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/has_log_monitoring_privileges.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 { ElasticsearchClient } from '@kbn/core/server'; +import { cluster, indices } from './monitoring_config'; + +export async function hasLogMonitoringPrivileges( + esClient: ElasticsearchClient +) { + const res = await esClient.security.hasPrivileges({ + body: { + index: indices, + cluster: [...cluster, 'manage_own_api_key'], + }, + }); + + return res.has_all_requested; +} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/monitoring_config.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/monitoring_config.ts new file mode 100644 index 0000000000000..675dde6f25a4b --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/api_key/monitoring_config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const cluster = ['monitor']; + +export const privileges = ['auto_configure', 'create_doc']; + +export const indices = [ + { + names: ['logs-*-*', 'metrics-*-*'], + privileges, + }, +]; diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_cloud_urls.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_cloud_urls.ts new file mode 100644 index 0000000000000..62e6e5d84fcb9 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_cloud_urls.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 { decodeCloudId } from '@kbn/fleet-plugin/common'; + +export function getCloudUrls(cloudId: string) { + const decodedCloudId = decodeCloudId(cloudId); + if (decodedCloudId) { + return { + elasticsearchUrl: decodedCloudId.elasticsearchUrl, + kibanaUrl: decodedCloudId.kibanaUrl, + }; + } +} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_es_hosts.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_es_hosts.ts deleted file mode 100644 index f9d35da7d3534..0000000000000 --- a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_es_hosts.ts +++ /dev/null @@ -1,39 +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 { Client } from '@elastic/elasticsearch'; -import { CloudSetup } from '@kbn/cloud-plugin/server'; -import { decodeCloudId } from '@kbn/fleet-plugin/common'; - -const DEFAULT_ES_HOSTS = ['http://localhost:9200']; - -export function getESHosts({ - cloudSetup, - esClient, -}: { - cloudSetup: CloudSetup; - esClient: Client; -}): string[] { - if (cloudSetup.cloudId) { - const cloudUrl = decodeCloudId(cloudSetup.cloudId)?.elasticsearchUrl; - if (cloudUrl) { - return [cloudUrl]; - } - } - - const aliveConnections = esClient.connectionPool.connections.filter( - ({ status }) => status === 'alive' - ); - if (aliveConnections.length) { - return aliveConnections.map(({ url }) => { - const { protocol, host } = new URL(url); - return `${protocol}//${host}`; - }); - } - - return DEFAULT_ES_HOSTS; -} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_fallback_urls.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_fallback_urls.ts new file mode 100644 index 0000000000000..e47e1f16a3351 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_fallback_urls.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { CoreStart } from '@kbn/core/server'; + +export function getFallbackUrls(coreStart: CoreStart) { + const esClient = coreStart.elasticsearch.client.asInternalUser as Client; + const [elasticsearchUrl] = getElasticsearchUrl(esClient); + const kibanaUrl = getKibanaUrl(coreStart); + return { elasticsearchUrl, kibanaUrl }; +} + +function getElasticsearchUrl(esClient: Client): string[] { + const aliveConnections = esClient.connectionPool.connections.filter( + ({ status }) => status === 'alive' + ); + if (aliveConnections.length) { + return aliveConnections.map(({ url }) => { + const { protocol, host } = new URL(url); + return `${protocol}//${host}`; + }); + } + + return ['http://localhost:9200']; +} + +function getKibanaUrl({ http }: CoreStart) { + const basePath = http.basePath; + const { protocol, hostname, port } = http.getServerInfo(); + return `${protocol}://${hostname}:${port}${basePath + // Prepending on '' removes the serverBasePath + .prepend('/') + .slice(0, -1)}`; +} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_has_logs.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_has_logs.ts new file mode 100644 index 0000000000000..59e96b9797cc8 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_has_logs.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +export async function getHasLogs({ + dataset, + namespace, + esClient, +}: { + dataset: string; + namespace: string; + esClient: ElasticsearchClient; +}) { + try { + const { hits } = await esClient.search({ + index: `logs-${dataset}-${namespace}`, + terminate_after: 1, + }); + const total = hits.total as { value: number }; + return total.value > 0; + } catch (error) { + if (error.statusCode === 404) { + return false; + } + throw error; + } +} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_kibana_url.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_kibana_url.ts deleted file mode 100644 index 95110f4707576..0000000000000 --- a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/get_kibana_url.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CoreStart } from '@kbn/core/server'; - -export function getKibanaUrl({ http }: CoreStart, path = '') { - const basePath = http.basePath; - const { protocol, hostname, port } = http.getServerInfo(); - return `${protocol}://${hostname}:${port}${basePath.prepend(path)}`; -} diff --git a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/route.ts b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/route.ts index b83cc0d1c730c..d9498011d0374 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/custom_logs/route.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/custom_logs/route.ts @@ -6,25 +6,66 @@ */ import * as t from 'io-ts'; -import Boom from '@hapi/boom'; -import type { Client } from '@elastic/elasticsearch'; +import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key'; +import { ObservabilityOnboardingState } from '../../saved_objects/observability_onboarding_status'; import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route'; -import { getESHosts } from './get_es_hosts'; -import { getKibanaUrl } from './get_kibana_url'; -import { createShipperApiKey } from './create_shipper_api_key'; -import { saveObservabilityOnboardingState } from './save_observability_onboarding_state'; -import { - ObservabilityOnboardingState, - OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE, - SavedObservabilityOnboardingState, -} from '../../saved_objects/observability_onboarding_status'; +import { createShipperApiKey } from './api_key/create_shipper_api_key'; +import { hasLogMonitoringPrivileges } from './api_key/has_log_monitoring_privileges'; +import { getCloudUrls } from './get_cloud_urls'; +import { getFallbackUrls } from './get_fallback_urls'; +import { getHasLogs } from './get_has_logs'; import { getObservabilityOnboardingState } from './get_observability_onboarding_state'; -import { findLatestObservabilityOnboardingState } from './find_latest_observability_onboarding_state'; -import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key'; +import { saveObservabilityOnboardingState } from './save_observability_onboarding_state'; -const createApiKeyRoute = createObservabilityOnboardingServerRoute({ +const ELASTIC_AGENT_VERSION = '8.8.0'; // This should be defined from a source with the latest public release + +const logMonitoringPrivilegesRoute = createObservabilityOnboardingServerRoute({ + endpoint: 'GET /internal/observability_onboarding/custom_logs/privileges', + options: { tags: [] }, + + handler: async (resources): Promise<{ hasPrivileges: boolean }> => { + const { context } = resources; + + const { + elasticsearch: { client }, + } = await context.core; + + const hasPrivileges = await hasLogMonitoringPrivileges( + client.asCurrentUser + ); + + return { hasPrivileges }; + }, +}); + +const installShipperSetupRoute = createObservabilityOnboardingServerRoute({ endpoint: - 'POST /internal/observability_onboarding/custom_logs/install_shipper_setup', + 'GET /internal/observability_onboarding/custom_logs/install_shipper_setup', + options: { tags: [] }, + async handler(resources): Promise<{ + apiEndpoint: string; + scriptDownloadUrl: string; + elasticAgentVersion: string; + }> { + const { core, plugins } = resources; + const coreStart = await core.start(); + + const cloudId = plugins.cloud.setup.cloudId; + const { kibanaUrl } = + (cloudId && getCloudUrls(cloudId)) || getFallbackUrls(coreStart); + const scriptDownloadUrl = `${kibanaUrl}/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh`; + const apiEndpoint = `${kibanaUrl}/api/observability_onboarding`; + + return { + apiEndpoint, + scriptDownloadUrl, + elasticAgentVersion: ELASTIC_AGENT_VERSION, + }; + }, +}); + +const createApiKeyRoute = createObservabilityOnboardingServerRoute({ + endpoint: 'POST /internal/observability_onboarding/custom_logs/save', options: { tags: [] }, params: t.type({ body: t.type({ @@ -35,9 +76,6 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({ async handler(resources): Promise<{ apiKeyId: string; apiKeyEncoded: string; - apiEndpoint: string; - scriptDownloadUrl: string; - esHost: string; }> { const { context, @@ -45,18 +83,9 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({ body: { name, state }, }, core, - plugins, request, } = resources; const coreStart = await core.start(); - const scriptDownloadUrl = getKibanaUrl( - coreStart, - '/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh' - ); - const apiEndpoint = getKibanaUrl( - coreStart, - '/api/observability_onboarding/custom_logs' - ); const { elasticsearch: { client }, } = await context.core; @@ -64,10 +93,6 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({ client.asCurrentUser, name ); - const [esHost] = getESHosts({ - cloudSetup: plugins.cloud.setup, - esClient: coreStart.elasticsearch.client.asInternalUser as Client, - }); const savedObjectsClient = coreStart.savedObjects.getScopedClient(request); @@ -78,11 +103,8 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({ }); return { - apiKeyId, // key the status off this + apiKeyId, apiKeyEncoded, - apiEndpoint, - scriptDownloadUrl, - esHost, }; }, }); @@ -108,7 +130,7 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({ request, core, } = resources; - const { apiKeyId } = getAuthenticationAPIKey(request); + const authApiKey = getAuthenticationAPIKey(request); const coreStart = await core.start(); const savedObjectsClient = coreStart.savedObjects.createInternalRepository(); @@ -116,7 +138,7 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({ const savedObservabilityOnboardingState = await getObservabilityOnboardingState({ savedObjectsClient, - apiKeyId, + apiKeyId: authApiKey?.apiKeyId as string, }); if (!savedObservabilityOnboardingState) { @@ -130,7 +152,7 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({ savedObservabilityOnboardingState; await saveObservabilityOnboardingState({ savedObjectsClient, - apiKeyId, + apiKeyId: authApiKey?.apiKeyId as string, observabilityOnboardingState: { ...observabilityOnboardingState, progress: { @@ -143,22 +165,21 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({ }, }); -const getStateRoute = createObservabilityOnboardingServerRoute({ - endpoint: 'GET /internal/observability_onboarding/custom_logs/state', +const getProgressRoute = createObservabilityOnboardingServerRoute({ + endpoint: 'GET /internal/observability_onboarding/custom_logs/progress', options: { tags: [] }, params: t.type({ query: t.type({ apiKeyId: t.string, }), }), - async handler(resources): Promise<{ - savedObservabilityOnboardingState: SavedObservabilityOnboardingState | null; - }> { + async handler(resources): Promise<{ progress: Record }> { const { params: { query: { apiKeyId }, }, core, + request, } = resources; const coreStart = await core.start(); const savedObjectsClient = @@ -168,91 +189,42 @@ const getStateRoute = createObservabilityOnboardingServerRoute({ savedObjectsClient, apiKeyId, })) || null; - return { savedObservabilityOnboardingState }; - }, -}); + const progress = { ...savedObservabilityOnboardingState?.progress }; -const getLatestStateRoute = createObservabilityOnboardingServerRoute({ - endpoint: 'GET /internal/observability_onboarding/custom_logs/state/latest', - options: { tags: [] }, - async handler(resources): Promise<{ - savedObservabilityOnboardingState: SavedObservabilityOnboardingState | null; - }> { - const { core } = resources; - const coreStart = await core.start(); - const savedObjectsClient = - coreStart.savedObjects.createInternalRepository(); - const savedObservabilityOnboardingState = - (await findLatestObservabilityOnboardingState({ savedObjectsClient })) || - null; - return { savedObservabilityOnboardingState }; - }, -}); - -const customLogsExistsRoute = createObservabilityOnboardingServerRoute({ - endpoint: 'GET /internal/observability_onboarding/custom_logs/exists', - options: { tags: [] }, - params: t.type({ - query: t.type({ - dataset: t.string, - namespace: t.string, - }), - }), - async handler(resources): Promise<{ exists: boolean }> { - const { - core, - request, - params: { - query: { dataset, namespace }, - }, - } = resources; - const coreStart = await core.start(); const esClient = coreStart.elasticsearch.client.asScoped(request).asCurrentUser; - try { - const { hits } = await esClient.search({ - index: `logs-${dataset}-${namespace}`, - terminate_after: 1, - }); - const total = hits.total as { value: number }; - return { exists: total.value > 0 }; - } catch (error) { - if (error.statusCode === 404) { - return { exists: false }; + if (savedObservabilityOnboardingState) { + const { + state: { datasetName: dataset, namespace }, + } = savedObservabilityOnboardingState; + if (progress['ea-status'] === 'complete') { + try { + const hasLogs = await getHasLogs({ + dataset, + namespace, + esClient, + }); + if (hasLogs) { + progress['logs-ingest'] = 'complete'; + } else { + progress['logs-ingest'] = 'loading'; + } + } catch (error) { + progress['logs-ingest'] = 'warning'; + } + } else { + progress['logs-ingest'] = 'incomplete'; } - throw Boom.boomify(error, { - statusCode: error.statusCode, - message: error.message, - data: error.body, - }); } - }, -}); -const deleteStatesRoute = createObservabilityOnboardingServerRoute({ - endpoint: 'DELETE /internal/observability_onboarding/custom_logs/states', - options: { tags: [] }, - async handler(resources): Promise { - const { core } = resources; - const coreStart = await core.start(); - const savedObjectsClient = - coreStart.savedObjects.createInternalRepository(); - const findStatesResult = - await savedObjectsClient.find({ - type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE, - }); - const bulkDeleteResult = await savedObjectsClient.bulkDelete( - findStatesResult.saved_objects - ); - return { bulkDeleteResult }; + return { progress }; }, }); export const customLogsRouteRepository = { + ...logMonitoringPrivilegesRoute, + ...installShipperSetupRoute, ...createApiKeyRoute, ...stepProgressUpdateRoute, - ...getStateRoute, - ...getLatestStateRoute, - ...customLogsExistsRoute, - ...deleteStatesRoute, + ...getProgressRoute, }; diff --git a/x-pack/plugins/observability_onboarding/server/routes/elastic_agent/route.ts b/x-pack/plugins/observability_onboarding/server/routes/elastic_agent/route.ts index ac43446287bea..7d5505b79c9b9 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/elastic_agent/route.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/elastic_agent/route.ts @@ -5,31 +5,31 @@ * 2.0. */ -import type { Client } from '@elastic/elasticsearch'; import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key'; import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route'; -import { findLatestObservabilityOnboardingState } from '../custom_logs/find_latest_observability_onboarding_state'; -import { getESHosts } from '../custom_logs/get_es_hosts'; +import { getObservabilityOnboardingState } from '../custom_logs/get_observability_onboarding_state'; import { generateYml } from './generate_yml'; +import { getCloudUrls } from '../custom_logs/get_cloud_urls'; +import { getFallbackUrls } from '../custom_logs/get_fallback_urls'; const generateConfig = createObservabilityOnboardingServerRoute({ endpoint: 'GET /api/observability_onboarding/elastic_agent/config 2023-05-24', options: { tags: [] }, async handler(resources): Promise { const { core, plugins, request } = resources; - const { apiKeyId, apiKey } = getAuthenticationAPIKey(request); + const authApiKey = getAuthenticationAPIKey(request); const coreStart = await core.start(); const savedObjectsClient = coreStart.savedObjects.createInternalRepository(); - const esHost = getESHosts({ - cloudSetup: plugins.cloud.setup, - esClient: coreStart.elasticsearch.client.asInternalUser as Client, - }); + const cloudId = plugins.cloud.setup.cloudId; + const { elasticsearchUrl } = + (cloudId && getCloudUrls(cloudId)) || getFallbackUrls(coreStart); - const savedState = await findLatestObservabilityOnboardingState({ + const savedState = await getObservabilityOnboardingState({ savedObjectsClient, + apiKeyId: authApiKey?.apiKeyId ?? '', }); const yaml = generateYml({ @@ -37,8 +37,10 @@ const generateConfig = createObservabilityOnboardingServerRoute({ customConfigurations: savedState?.state.customConfigurations, logFilePaths: savedState?.state.logFilePaths, namespace: savedState?.state.namespace, - apiKey: `${apiKeyId}:${apiKey}`, - esHost, + apiKey: authApiKey + ? `${authApiKey?.apiKeyId}:${authApiKey?.apiKey}` + : '$API_KEY', + esHost: [elasticsearchUrl], logfileId: `custom-logs-${Date.now()}`, }); diff --git a/x-pack/plugins/observability_onboarding/server/routes/register_routes.ts b/x-pack/plugins/observability_onboarding/server/routes/register_routes.ts index 0220f64582396..120104ccca989 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/register_routes.ts @@ -14,6 +14,7 @@ import { routeValidationObject, } from '@kbn/server-route-repository'; import * as t from 'io-ts'; +import { ObservabilityOnboardingConfig } from '..'; import { ObservabilityOnboardingRequestHandlerContext } from '../types'; import { ObservabilityOnboardingRouteHandlerResources } from './types'; @@ -22,6 +23,7 @@ interface RegisterRoutes { repository: ServerRouteRepository; logger: Logger; plugins: ObservabilityOnboardingRouteHandlerResources['plugins']; + config: ObservabilityOnboardingConfig; } export function registerRoutes({ @@ -29,6 +31,7 @@ export function registerRoutes({ core, logger, plugins, + config, }: RegisterRoutes) { const routes = Object.values(repository); @@ -73,6 +76,7 @@ export function registerRoutes({ return coreStart; }, }, + config, })) as any; if (data === undefined) { diff --git a/x-pack/plugins/observability_onboarding/server/routes/types.ts b/x-pack/plugins/observability_onboarding/server/routes/types.ts index 6988561f84e44..d4933a6449052 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/types.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/types.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, KibanaRequest, Logger } from '@kbn/core/server'; import { ObservabilityOnboardingServerRouteRepository } from '.'; +import { ObservabilityOnboardingConfig } from '..'; import { ObservabilityOnboardingPluginSetupDependencies, ObservabilityOnboardingPluginStartDependencies, @@ -30,6 +31,7 @@ export interface ObservabilityOnboardingRouteHandlerResources { setup: CoreSetup; start: () => Promise; }; + config: ObservabilityOnboardingConfig; } export interface ObservabilityOnboardingRouteCreateOptions { diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index 678f7dc01f2b8..332d7adde036f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -21,253 +21,223 @@ describe('Endpoint Authz service', () => { let fleetAuthz: FleetAuthz; let userRoles: string[]; + const responseConsolePrivileges = CONSOLE_RESPONSE_ACTION_COMMANDS.slice().reduce< + ResponseConsoleRbacControls[] + >((acc, e) => { + const item = RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL[e]; + if (!acc.includes(item)) { + acc.push(item); + } + return acc; + }, []); + beforeEach(() => { licenseService = createLicenseServiceMock(); fleetAuthz = createFleetAuthzMock(); - userRoles = ['superuser']; + userRoles = []; }); describe('calculateEndpointAuthz()', () => { - describe('and `fleet.all` access is true', () => { - it.each([ - ['canAccessFleet'], - ['canAccessEndpointManagement'], - ['canIsolateHost'], - ['canUnIsolateHost'], - ['canKillProcess'], - ['canSuspendProcess'], - ['canGetRunningProcesses'], - ])('should set `%s` to `true`', (authProperty) => { - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)[authProperty]).toBe( - true - ); - }); - - it('should set `canIsolateHost` to false if not proper license', () => { - licenseService.isPlatinumPlus.mockReturnValue(false); + it('should set `canIsolateHost` to false if not proper license', () => { + licenseService.isPlatinumPlus.mockReturnValue(false); - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canIsolateHost).toBe( - false - ); - }); + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canIsolateHost).toBe( + false + ); + }); - it('should set `canKillProcess` to false if not proper license', () => { - licenseService.isEnterprise.mockReturnValue(false); + it('should set `canKillProcess` to false if not proper license', () => { + licenseService.isEnterprise.mockReturnValue(false); - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canKillProcess).toBe( - false - ); - }); + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canKillProcess).toBe( + false + ); + }); - it('should set `canSuspendProcess` to false if not proper license', () => { - licenseService.isEnterprise.mockReturnValue(false); + it('should set `canSuspendProcess` to false if not proper license', () => { + licenseService.isEnterprise.mockReturnValue(false); - expect( - calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canSuspendProcess - ).toBe(false); - }); + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canSuspendProcess).toBe( + false + ); + }); - it('should set `canGetRunningProcesses` to false if not proper license', () => { - licenseService.isEnterprise.mockReturnValue(false); + it('should set `canGetRunningProcesses` to false if not proper license', () => { + licenseService.isEnterprise.mockReturnValue(false); - expect( - calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canGetRunningProcesses - ).toBe(false); - }); + expect( + calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canGetRunningProcesses + ).toBe(false); + }); - it('should set `canUnIsolateHost` to true even if not proper license', () => { - licenseService.isPlatinumPlus.mockReturnValue(false); + it('should set `canUnIsolateHost` to true even if not proper license', () => { + licenseService.isPlatinumPlus.mockReturnValue(false); - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canUnIsolateHost).toBe( - true - ); - }); + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canUnIsolateHost).toBe( + true + ); + }); - it(`should allow Host Isolation Exception read/delete when license is not Platinum+, but entries exist`, () => { - licenseService.isPlatinumPlus.mockReturnValue(false); + it(`should allow Host Isolation Exception read/delete when license is not Platinum+`, () => { + licenseService.isPlatinumPlus.mockReturnValue(false); - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, false, true)).toEqual( - expect.objectContaining({ - canWriteHostIsolationExceptions: false, - canReadHostIsolationExceptions: true, - canDeleteHostIsolationExceptions: true, - }) - ); - }); + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)).toEqual( + expect.objectContaining({ + canWriteHostIsolationExceptions: false, + canAccessHostIsolationExceptions: false, + canReadHostIsolationExceptions: true, + canDeleteHostIsolationExceptions: true, + }) + ); }); - describe('and `fleet.all` access is false', () => { - beforeEach(() => { - fleetAuthz.fleet.all = false; - userRoles = []; - }); - - it.each([ - ['canAccessFleet'], - ['canAccessEndpointManagement'], - ['canIsolateHost'], - ['canUnIsolateHost'], - ['canKillProcess'], - ['canSuspendProcess'], - ['canGetRunningProcesses'], - ])('should set `%s` to `false`', (authProperty) => { - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)[authProperty]).toBe( - false - ); - }); + it('should not give canAccessFleet if `fleet.all` is false', () => { + fleetAuthz.fleet.all = false; + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessFleet).toBe( + false + ); + }); - it('should set `canUnIsolateHost` to false when policy is also not platinum', () => { - licenseService.isPlatinumPlus.mockReturnValue(false); + it('should not give canAccessEndpointManagement if not superuser', () => { + userRoles = []; + expect( + calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessEndpointManagement + ).toBe(false); + }); - expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canUnIsolateHost).toBe( - false - ); - }); + it('should give canAccessFleet if `fleet.all` is true', () => { + fleetAuthz.fleet.all = true; + expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessFleet).toBe( + true + ); }); - describe('and endpoint rbac is enabled', () => { - const responseConsolePrivileges = CONSOLE_RESPONSE_ACTION_COMMANDS.slice().reduce< - ResponseConsoleRbacControls[] - >((acc, e) => { - const item = RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL[e]; - if (!acc.includes(item)) { - acc.push(item); - } - return acc; - }, []); + it('should give canAccessEndpointManagement if superuser', () => { + userRoles = ['superuser']; + expect( + calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessEndpointManagement + ).toBe(true); + userRoles = []; + }); - beforeEach(() => { - userRoles = []; - }); + it.each<[EndpointAuthzKeyList[number], string]>([ + ['canWriteEndpointList', 'writeEndpointList'], + ['canReadEndpointList', 'readEndpointList'], + ['canWritePolicyManagement', 'writePolicyManagement'], + ['canReadPolicyManagement', 'readPolicyManagement'], + ['canWriteActionsLogManagement', 'writeActionsLogManagement'], + ['canReadActionsLogManagement', 'readActionsLogManagement'], + ['canAccessEndpointActionsLogManagement', 'readActionsLogManagement'], + ['canIsolateHost', 'writeHostIsolation'], + ['canUnIsolateHost', 'writeHostIsolation'], + ['canKillProcess', 'writeProcessOperations'], + ['canSuspendProcess', 'writeProcessOperations'], + ['canGetRunningProcesses', 'writeProcessOperations'], + ['canWriteExecuteOperations', 'writeExecuteOperations'], + ['canWriteFileOperations', 'writeFileOperations'], + ['canWriteTrustedApplications', 'writeTrustedApplications'], + ['canReadTrustedApplications', 'readTrustedApplications'], + ['canWriteHostIsolationExceptions', 'writeHostIsolationExceptions'], + ['canAccessHostIsolationExceptions', 'accessHostIsolationExceptions'], + ['canReadHostIsolationExceptions', 'readHostIsolationExceptions'], + ['canDeleteHostIsolationExceptions', 'deleteHostIsolationExceptions'], + ['canWriteBlocklist', 'writeBlocklist'], + ['canReadBlocklist', 'readBlocklist'], + ['canWriteEventFilters', 'writeEventFilters'], + ['canReadEventFilters', 'readEventFilters'], + ])('%s should be true if `packagePrivilege.%s` is `true`', (auth) => { + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + expect(authz[auth]).toBe(true); + }); - it.each<[EndpointAuthzKeyList[number], string]>([ - ['canWriteEndpointList', 'writeEndpointList'], - ['canReadEndpointList', 'readEndpointList'], - ['canWritePolicyManagement', 'writePolicyManagement'], - ['canReadPolicyManagement', 'readPolicyManagement'], - ['canWriteActionsLogManagement', 'writeActionsLogManagement'], - ['canReadActionsLogManagement', 'readActionsLogManagement'], - ['canAccessEndpointActionsLogManagement', 'readActionsLogManagement'], - ['canIsolateHost', 'writeHostIsolation'], - ['canUnIsolateHost', 'writeHostIsolation'], - ['canKillProcess', 'writeProcessOperations'], - ['canSuspendProcess', 'writeProcessOperations'], - ['canGetRunningProcesses', 'writeProcessOperations'], - ['canWriteExecuteOperations', 'writeExecuteOperations'], - ['canWriteFileOperations', 'writeFileOperations'], - ['canWriteTrustedApplications', 'writeTrustedApplications'], - ['canReadTrustedApplications', 'readTrustedApplications'], - ['canWriteHostIsolationExceptions', 'writeHostIsolationExceptions'], - ['canReadHostIsolationExceptions', 'readHostIsolationExceptions'], - ['canWriteBlocklist', 'writeBlocklist'], - ['canReadBlocklist', 'readBlocklist'], - ['canWriteEventFilters', 'writeEventFilters'], - ['canReadEventFilters', 'readEventFilters'], - ])('%s should be true if `packagePrivilege.%s` is `true`', (auth) => { - const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); - expect(authz[auth]).toBe(true); + it.each<[EndpointAuthzKeyList[number], string[]]>([ + ['canWriteEndpointList', ['writeEndpointList']], + ['canReadEndpointList', ['readEndpointList']], + ['canWritePolicyManagement', ['writePolicyManagement']], + ['canReadPolicyManagement', ['readPolicyManagement']], + ['canWriteActionsLogManagement', ['writeActionsLogManagement']], + ['canReadActionsLogManagement', ['readActionsLogManagement']], + ['canAccessEndpointActionsLogManagement', ['readActionsLogManagement']], + ['canIsolateHost', ['writeHostIsolation']], + ['canUnIsolateHost', ['writeHostIsolationRelease']], + ['canKillProcess', ['writeProcessOperations']], + ['canSuspendProcess', ['writeProcessOperations']], + ['canGetRunningProcesses', ['writeProcessOperations']], + ['canWriteExecuteOperations', ['writeExecuteOperations']], + ['canWriteFileOperations', ['writeFileOperations']], + ['canWriteTrustedApplications', ['writeTrustedApplications']], + ['canReadTrustedApplications', ['readTrustedApplications']], + ['canWriteHostIsolationExceptions', ['writeHostIsolationExceptions']], + ['canAccessHostIsolationExceptions', ['accessHostIsolationExceptions']], + ['canReadHostIsolationExceptions', ['readHostIsolationExceptions']], + ['canDeleteHostIsolationExceptions', ['deleteHostIsolationExceptions']], + ['canWriteBlocklist', ['writeBlocklist']], + ['canReadBlocklist', ['readBlocklist']], + ['canWriteEventFilters', ['writeEventFilters']], + ['canReadEventFilters', ['readEventFilters']], + // all dependent privileges are false and so it should be false + ['canAccessResponseConsole', responseConsolePrivileges], + ])('%s should be false if `packagePrivilege.%s` is `false`', (auth, privileges) => { + privileges.forEach((privilege) => { + fleetAuthz.packagePrivileges!.endpoint.actions[privilege].executePackageAction = false; }); + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + expect(authz[auth]).toBe(false); + }); - it.each<[EndpointAuthzKeyList[number], string[]]>([ - ['canWriteEndpointList', ['writeEndpointList']], - ['canReadEndpointList', ['writeEndpointList', 'readEndpointList']], - ['canWritePolicyManagement', ['writePolicyManagement']], - ['canReadPolicyManagement', ['writePolicyManagement', 'readPolicyManagement']], - ['canWriteActionsLogManagement', ['writeActionsLogManagement']], - ['canReadActionsLogManagement', ['writeActionsLogManagement', 'readActionsLogManagement']], - [ - 'canAccessEndpointActionsLogManagement', - ['writeActionsLogManagement', 'readActionsLogManagement'], - ], - ['canIsolateHost', ['writeHostIsolation']], - ['canUnIsolateHost', ['writeHostIsolation']], - ['canKillProcess', ['writeProcessOperations']], - ['canSuspendProcess', ['writeProcessOperations']], - ['canGetRunningProcesses', ['writeProcessOperations']], - ['canWriteExecuteOperations', ['writeExecuteOperations']], - ['canWriteFileOperations', ['writeFileOperations']], - ['canWriteTrustedApplications', ['writeTrustedApplications']], - ['canReadTrustedApplications', ['writeTrustedApplications', 'readTrustedApplications']], - ['canWriteHostIsolationExceptions', ['writeHostIsolationExceptions']], - [ - 'canReadHostIsolationExceptions', - ['writeHostIsolationExceptions', 'readHostIsolationExceptions'], - ], - ['canWriteBlocklist', ['writeBlocklist']], - ['canReadBlocklist', ['writeBlocklist', 'readBlocklist']], - ['canWriteEventFilters', ['writeEventFilters']], - ['canReadEventFilters', ['writeEventFilters', 'readEventFilters']], - // all dependent privileges are false and so it should be false - ['canAccessResponseConsole', responseConsolePrivileges], - ])('%s should be false if `packagePrivilege.%s` is `false`', (auth, privileges) => { - // read permission checks for write || read so we need to set both to false + it.each<[EndpointAuthzKeyList[number], string[]]>([ + ['canWriteEndpointList', ['writeEndpointList']], + ['canReadEndpointList', ['readEndpointList']], + ['canWritePolicyManagement', ['writePolicyManagement']], + ['canReadPolicyManagement', ['readPolicyManagement']], + ['canWriteActionsLogManagement', ['writeActionsLogManagement']], + ['canReadActionsLogManagement', ['readActionsLogManagement']], + ['canAccessEndpointActionsLogManagement', ['readActionsLogManagement']], + ['canIsolateHost', ['writeHostIsolation']], + ['canUnIsolateHost', ['writeHostIsolationRelease']], + ['canKillProcess', ['writeProcessOperations']], + ['canSuspendProcess', ['writeProcessOperations']], + ['canGetRunningProcesses', ['writeProcessOperations']], + ['canWriteExecuteOperations', ['writeExecuteOperations']], + ['canWriteFileOperations', ['writeFileOperations']], + ['canWriteTrustedApplications', ['writeTrustedApplications']], + ['canReadTrustedApplications', ['readTrustedApplications']], + ['canWriteHostIsolationExceptions', ['writeHostIsolationExceptions']], + ['canAccessHostIsolationExceptions', ['accessHostIsolationExceptions']], + ['canReadHostIsolationExceptions', ['readHostIsolationExceptions']], + ['canWriteBlocklist', ['writeBlocklist']], + ['canReadBlocklist', ['readBlocklist']], + ['canWriteEventFilters', ['writeEventFilters']], + ['canReadEventFilters', ['readEventFilters']], + // all dependent privileges are false and so it should be false + ['canAccessResponseConsole', responseConsolePrivileges], + ])( + '%s should be false if `packagePrivilege.%s` is `false` and user roles is undefined', + (auth, privileges) => { privileges.forEach((privilege) => { fleetAuthz.packagePrivileges!.endpoint.actions[privilege].executePackageAction = false; }); - const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, undefined); expect(authz[auth]).toBe(false); - }); + } + ); - it.each<[EndpointAuthzKeyList[number], string[]]>([ - ['canWriteEndpointList', ['writeEndpointList']], - ['canReadEndpointList', ['writeEndpointList', 'readEndpointList']], - ['canWritePolicyManagement', ['writePolicyManagement']], - ['canReadPolicyManagement', ['writePolicyManagement', 'readPolicyManagement']], - ['canWriteActionsLogManagement', ['writeActionsLogManagement']], - ['canReadActionsLogManagement', ['writeActionsLogManagement', 'readActionsLogManagement']], - [ - 'canAccessEndpointActionsLogManagement', - ['writeActionsLogManagement', 'readActionsLogManagement'], - ], - ['canIsolateHost', ['writeHostIsolation']], - ['canUnIsolateHost', ['writeHostIsolation']], - ['canKillProcess', ['writeProcessOperations']], - ['canSuspendProcess', ['writeProcessOperations']], - ['canGetRunningProcesses', ['writeProcessOperations']], - ['canWriteExecuteOperations', ['writeExecuteOperations']], - ['canWriteFileOperations', ['writeFileOperations']], - ['canWriteTrustedApplications', ['writeTrustedApplications']], - ['canReadTrustedApplications', ['writeTrustedApplications', 'readTrustedApplications']], - ['canWriteHostIsolationExceptions', ['writeHostIsolationExceptions']], - [ - 'canReadHostIsolationExceptions', - ['writeHostIsolationExceptions', 'readHostIsolationExceptions'], - ], - ['canWriteBlocklist', ['writeBlocklist']], - ['canReadBlocklist', ['writeBlocklist', 'readBlocklist']], - ['canWriteEventFilters', ['writeEventFilters']], - ['canReadEventFilters', ['writeEventFilters', 'readEventFilters']], - // all dependent privileges are false and so it should be false - ['canAccessResponseConsole', responseConsolePrivileges], - ])( - '%s should be false if `packagePrivilege.%s` is `false` and user roles is undefined', - (auth, privileges) => { - // read permission checks for write || read so we need to set both to false - privileges.forEach((privilege) => { - fleetAuthz.packagePrivileges!.endpoint.actions[privilege].executePackageAction = false; - }); - const authz = calculateEndpointAuthz(licenseService, fleetAuthz, undefined, true); - expect(authz[auth]).toBe(false); - } - ); - - it.each(responseConsolePrivileges)( - 'canAccessResponseConsole should be true if %s for CONSOLE privileges is true', - (responseConsolePrivilege) => { - // set all to false - responseConsolePrivileges.forEach((p) => { - fleetAuthz.packagePrivileges!.endpoint.actions[p].executePackageAction = false; - }); - // set one of them to true - fleetAuthz.packagePrivileges!.endpoint.actions[ - responseConsolePrivilege - ].executePackageAction = true; + it.each(responseConsolePrivileges)( + 'canAccessResponseConsole should be true if %s for CONSOLE privileges is true', + (responseConsolePrivilege) => { + // set all to false + responseConsolePrivileges.forEach((p) => { + fleetAuthz.packagePrivileges!.endpoint.actions[p].executePackageAction = false; + }); + // set one of them to true + fleetAuthz.packagePrivileges!.endpoint.actions[ + responseConsolePrivilege + ].executePackageAction = true; - const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true); - expect(authz.canAccessResponseConsole).toBe(true); - } - ); - }); + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + expect(authz.canAccessResponseConsole).toBe(true); + } + ); }); describe('getEndpointAuthzInitialState()', () => { @@ -287,7 +257,7 @@ describe('Endpoint Authz service', () => { canWriteActionsLogManagement: false, canReadActionsLogManagement: false, canIsolateHost: false, - canUnIsolateHost: true, + canUnIsolateHost: false, canKillProcess: false, canSuspendProcess: false, canGetRunningProcesses: false, @@ -297,6 +267,7 @@ describe('Endpoint Authz service', () => { canWriteTrustedApplications: false, canReadTrustedApplications: false, canWriteHostIsolationExceptions: false, + canAccessHostIsolationExceptions: false, canReadHostIsolationExceptions: false, canWriteBlocklist: false, canReadBlocklist: false, diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index 1a036dea9f8e5..26524dcc17888 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -19,27 +19,12 @@ import type { MaybeImmutable } from '../../types'; * level, use `calculateEndpointAuthz()` * * @param fleetAuthz - * @param isEndpointRbacEnabled - * @param isSuperuser * @param privilege */ export function hasKibanaPrivilege( fleetAuthz: FleetAuthz, - isEndpointRbacEnabled: boolean, - isSuperuser: boolean = false, privilege: keyof typeof ENDPOINT_PRIVILEGES ): boolean { - // user is superuser, always return true - if (isSuperuser) { - return true; - } - - // not superuser and FF not enabled, no access - if (!isEndpointRbacEnabled) { - return false; - } - - // FF enabled, access based on privileges return fleetAuthz.packagePrivileges?.endpoint?.actions[privilege].executePackageAction ?? false; } @@ -50,181 +35,58 @@ export function hasKibanaPrivilege( * @param licenseService * @param fleetAuthz * @param userRoles - * @param isEndpointRbacEnabled - * @param permissions - * @param hasHostIsolationExceptionsItems if set to `true`, then Host Isolation Exceptions related authz properties - * may be adjusted to account for a license downgrade scenario */ - -// eslint-disable-next-line complexity export const calculateEndpointAuthz = ( licenseService: LicenseService, fleetAuthz: FleetAuthz, - userRoles: MaybeImmutable = [], - isEndpointRbacEnabled: boolean = false, - hasHostIsolationExceptionsItems: boolean = false + userRoles: MaybeImmutable = [] ): EndpointAuthz => { const isPlatinumPlusLicense = licenseService.isPlatinumPlus(); const isEnterpriseLicense = licenseService.isEnterprise(); const hasEndpointManagementAccess = userRoles.includes('superuser'); - const canWriteSecuritySolution = hasKibanaPrivilege( - fleetAuthz, - true, - hasEndpointManagementAccess, - 'writeSecuritySolution' - ); - const canReadSecuritySolution = - canWriteSecuritySolution || - hasKibanaPrivilege(fleetAuthz, true, hasEndpointManagementAccess, 'readSecuritySolution'); - const canWriteEndpointList = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeEndpointList' - ); - const canReadEndpointList = - canWriteEndpointList || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readEndpointList' - ); - const canWritePolicyManagement = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writePolicyManagement' - ); - const canReadPolicyManagement = - canWritePolicyManagement || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readPolicyManagement' - ); - const canWriteActionsLogManagement = hasKibanaPrivilege( + const canWriteSecuritySolution = hasKibanaPrivilege(fleetAuthz, 'writeSecuritySolution'); + const canReadSecuritySolution = hasKibanaPrivilege(fleetAuthz, 'readSecuritySolution'); + const canWriteEndpointList = hasKibanaPrivilege(fleetAuthz, 'writeEndpointList'); + const canReadEndpointList = hasKibanaPrivilege(fleetAuthz, 'readEndpointList'); + const canWritePolicyManagement = hasKibanaPrivilege(fleetAuthz, 'writePolicyManagement'); + const canReadPolicyManagement = hasKibanaPrivilege(fleetAuthz, 'readPolicyManagement'); + const canWriteActionsLogManagement = hasKibanaPrivilege(fleetAuthz, 'writeActionsLogManagement'); + const canReadActionsLogManagement = hasKibanaPrivilege(fleetAuthz, 'readActionsLogManagement'); + const canIsolateHost = hasKibanaPrivilege(fleetAuthz, 'writeHostIsolation'); + const canUnIsolateHost = hasKibanaPrivilege(fleetAuthz, 'writeHostIsolationRelease'); + const canWriteProcessOperations = hasKibanaPrivilege(fleetAuthz, 'writeProcessOperations'); + const canWriteTrustedApplications = hasKibanaPrivilege(fleetAuthz, 'writeTrustedApplications'); + const canReadTrustedApplications = hasKibanaPrivilege(fleetAuthz, 'readTrustedApplications'); + const canWriteHostIsolationExceptions = hasKibanaPrivilege( fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeActionsLogManagement' - ); - const canReadActionsLogManagement = - canWriteActionsLogManagement || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readActionsLogManagement' - ); - const canIsolateHost = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeHostIsolation' - ); - const canWriteProcessOperations = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeProcessOperations' - ); - const canWriteTrustedApplications = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeTrustedApplications' - ); - const canReadTrustedApplications = - canWriteTrustedApplications || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readTrustedApplications' - ); - - const hasWriteHostIsolationExceptionsPermission = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, 'writeHostIsolationExceptions' ); - const canWriteHostIsolationExceptions = - hasWriteHostIsolationExceptionsPermission && isPlatinumPlusLicense; - - const hasReadHostIsolationExceptionsPermission = - hasWriteHostIsolationExceptionsPermission || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readHostIsolationExceptions' - ); - // Calculate the Host Isolation Exceptions Authz. Some of these authz properties could be - // set to `true` in cases where license was downgraded, but entries still exist. - const canReadHostIsolationExceptions = - canWriteHostIsolationExceptions || - (hasReadHostIsolationExceptionsPermission && - // We still allow `read` if not Platinum license, but entries exists for HIE - (isPlatinumPlusLicense || hasHostIsolationExceptionsItems)); - - const canDeleteHostIsolationExceptions = - canWriteHostIsolationExceptions || - // Should be able to delete if host isolation exceptions exists and license is not platinum+ - (hasWriteHostIsolationExceptionsPermission && - !isPlatinumPlusLicense && - hasHostIsolationExceptionsItems); - - const canWriteBlocklist = hasKibanaPrivilege( + const canReadHostIsolationExceptions = hasKibanaPrivilege( fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeBlocklist' + 'readHostIsolationExceptions' ); - const canReadBlocklist = - canWriteBlocklist || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readBlocklist' - ); - const canWriteEventFilters = hasKibanaPrivilege( + const canAccessHostIsolationExceptions = hasKibanaPrivilege( fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeEventFilters' + 'accessHostIsolationExceptions' ); - const canReadEventFilters = - canWriteEventFilters || - hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'readEventFilters' - ); - const canWriteFileOperations = hasKibanaPrivilege( + const canDeleteHostIsolationExceptions = hasKibanaPrivilege( fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeFileOperations' + 'deleteHostIsolationExceptions' ); + const canWriteBlocklist = hasKibanaPrivilege(fleetAuthz, 'writeBlocklist'); + const canReadBlocklist = hasKibanaPrivilege(fleetAuthz, 'readBlocklist'); + const canWriteEventFilters = hasKibanaPrivilege(fleetAuthz, 'writeEventFilters'); + const canReadEventFilters = hasKibanaPrivilege(fleetAuthz, 'readEventFilters'); + const canWriteFileOperations = hasKibanaPrivilege(fleetAuthz, 'writeFileOperations'); - const canWriteExecuteOperations = hasKibanaPrivilege( - fleetAuthz, - isEndpointRbacEnabled, - hasEndpointManagementAccess, - 'writeExecuteOperations' - ); + const canWriteExecuteOperations = hasKibanaPrivilege(fleetAuthz, 'writeExecuteOperations'); return { canWriteSecuritySolution, canReadSecuritySolution, - canAccessFleet: fleetAuthz?.fleet.all ?? userRoles.includes('superuser'), - canAccessEndpointManagement: hasEndpointManagementAccess, + canAccessFleet: fleetAuthz?.fleet.all ?? false, + canAccessEndpointManagement: hasEndpointManagementAccess, // TODO: is this one deprecated? it is the only place we need to check for superuser. canCreateArtifactsByPolicy: isPlatinumPlusLicense, canWriteEndpointList, canReadEndpointList, @@ -235,13 +97,14 @@ export const calculateEndpointAuthz = ( canAccessEndpointActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense, // Response Actions canIsolateHost: canIsolateHost && isPlatinumPlusLicense, - canUnIsolateHost: canIsolateHost, + canUnIsolateHost, canKillProcess: canWriteProcessOperations && isEnterpriseLicense, canSuspendProcess: canWriteProcessOperations && isEnterpriseLicense, canGetRunningProcesses: canWriteProcessOperations && isEnterpriseLicense, canAccessResponseConsole: isEnterpriseLicense && (canIsolateHost || + canUnIsolateHost || canWriteProcessOperations || canWriteFileOperations || canWriteExecuteOperations), @@ -250,7 +113,8 @@ export const calculateEndpointAuthz = ( // artifacts canWriteTrustedApplications, canReadTrustedApplications, - canWriteHostIsolationExceptions, + canWriteHostIsolationExceptions: canWriteHostIsolationExceptions && isPlatinumPlusLicense, + canAccessHostIsolationExceptions: canAccessHostIsolationExceptions && isPlatinumPlusLicense, canReadHostIsolationExceptions, canDeleteHostIsolationExceptions, canWriteBlocklist, @@ -275,7 +139,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => { canWriteActionsLogManagement: false, canReadActionsLogManagement: false, canIsolateHost: false, - canUnIsolateHost: true, + canUnIsolateHost: false, canKillProcess: false, canSuspendProcess: false, canGetRunningProcesses: false, @@ -285,6 +149,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => { canWriteTrustedApplications: false, canReadTrustedApplications: false, canWriteHostIsolationExceptions: false, + canAccessHostIsolationExceptions: false, canReadHostIsolationExceptions: false, canDeleteHostIsolationExceptions: false, canWriteBlocklist: false, diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/mocks.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/mocks.ts index 0abfe82b3f4d2..b8f5e5cb3ef4d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/mocks.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/mocks.ts @@ -20,8 +20,6 @@ export const getEndpointAuthzInitialStateMock = ( return mockPrivileges; }, {} as EndpointAuthz), - // this one is currently treated special in that everyone can un-isolate - canUnIsolateHost: true, ...overrides, }; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts index 3941a390085ea..a64b6a9b30a60 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts @@ -63,6 +63,7 @@ export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMA export type ResponseConsoleRbacControls = | 'writeHostIsolation' + | 'writeHostIsolationRelease' | 'writeProcessOperations' | 'writeFileOperations' | 'writeExecuteOperations'; @@ -75,7 +76,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL: Record< ResponseConsoleRbacControls > = Object.freeze({ isolate: 'writeHostIsolation', - release: 'writeHostIsolation', + release: 'writeHostIsolationRelease', 'kill-process': 'writeProcessOperations', 'suspend-process': 'writeProcessOperations', processes: 'writeProcessOperations', diff --git a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts index ab7d097ffcd10..23ed5cbe7c439 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts @@ -58,6 +58,12 @@ export interface EndpointAuthz { canWriteHostIsolationExceptions: boolean; /** if user has read permissions for host isolation exceptions */ canReadHostIsolationExceptions: boolean; + /** + * if user has permissions to access host isolation exceptions. This could be set to false, while + * `canReadHostIsolationExceptions` is true in cases where the license might have been downgraded. + * It is used to show the UI elements that allow users to navigate to the host isolation exceptions. + */ + canAccessHostIsolationExceptions: boolean; /** * if user has permissions to delete host isolation exceptions. This could be set to true, while * `canWriteHostIsolationExceptions` is false in cases where the license might have been downgraded. diff --git a/x-pack/plugins/security_solution/common/types/app_features.ts b/x-pack/plugins/security_solution/common/types/app_features.ts index 203ce2c6f81f1..b3955232fd209 100644 --- a/x-pack/plugins/security_solution/common/types/app_features.ts +++ b/x-pack/plugins/security_solution/common/types/app_features.ts @@ -10,6 +10,14 @@ export enum AppFeatureSecurityKey { * Enables Advanced Insights (Entity Risk, GenAI) */ advancedInsights = 'advanced_insights', + /** + * Enables Endpoint Response Actions like isolate host, trusted apps, blocklist, etc. + */ + endpointResponseActions = 'endpoint_response_actions', + /** + * Enables Endpoint Exceptions like isolate host, trusted apps, blocklist, etc. + */ + endpointExceptions = 'endpoint_exceptions', } export enum AppFeatureCasesKey { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts deleted file mode 100644 index ed6c940de2370..0000000000000 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts +++ /dev/null @@ -1,162 +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 { - DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_CONTENT, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_CONTENT, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_CONTENT, - DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT, - DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP, - DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT, -} from '../../../../screens/document_expandable_flyout'; -import { - expandDocumentDetailsExpandableFlyoutLeftSection, - expandFirstAlertExpandableFlyout, - openGraphAnalyzer, - openHistoryTab, - openInsightsTab, - openInvestigationTab, - openSessionView, - openVisualizeTab, - openEntities, - openThreatIntelligence, - openPrevalence, - openCorrelations, -} from '../../../../tasks/document_expandable_flyout'; -import { cleanKibana } from '../../../../tasks/common'; -import { login, visit } from '../../../../tasks/login'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { getNewRule } from '../../../../objects/rule'; -import { ALERTS_URL } from '../../../../urls/navigation'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; - -describe( - 'Alert details expandable flyout left panel', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - before(() => { - cleanKibana(); - createRule(getNewRule()); - }); - - beforeEach(() => { - login(); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - }); - - it('should display 4 tabs in the header', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) - .should('be.visible') - .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB).should('be.visible').and('have.text', 'History'); - }); - - it.skip('should display tab content when switching tabs', () => { - openVisualizeTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - - openInsightsTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - - openInvestigationTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - - openHistoryTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT).should('be.visible'); - }); - - describe.skip('visualize tab', () => { - it('should display a button group with 2 button in the visualize tab', () => { - openVisualizeTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Session View'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) - .should('be.visible') - .and('have.text', 'Analyzer Graph'); - }); - - it('should display content when switching buttons', () => { - openVisualizeTab(); - openSessionView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT).should('be.visible'); - - openGraphAnalyzer(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); - }); - }); - - describe.skip('insights tab', () => { - it('should display a button group with 4 button in the insights tab', () => { - openInsightsTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) - .should('be.visible') - .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) - .should('be.visible') - .and('have.text', 'Correlations'); - }); - - it('should display content when switching buttons', () => { - openInsightsTab(); - openEntities(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); - - openThreatIntelligence(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_CONTENT) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); - - openPrevalence(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_CONTENT) - .should('be.visible') - .and('have.text', 'Prevalence'); - - openCorrelations(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_CONTENT) - .should('be.visible') - .and('have.text', 'Correlations'); - }); - }); - - describe.skip('investigation tab', () => { - it('should display investigation guide', () => { - openInvestigationTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - }); - }); - } -); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts index a38f5a374a164..43531feb67d73 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts @@ -5,13 +5,18 @@ * 2.0. */ -import { ANALYZER_NODE } from '../../../../screens/alerts'; -import { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT } from '../../../../screens/document_expandable_flyout'; import { - expandFirstAlertExpandableFlyout, - openGraphAnalyzer, - expandDocumentDetailsExpandableFlyoutLeftSection, -} from '../../../../tasks/document_expandable_flyout'; + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON, + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT, +} from '../../../../screens/expandable_flyout/alert_details_left_panel_analyzer_graph_tab'; +import { + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB, + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { openGraphAnalyzerTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_analyzer_graph_tab'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { ANALYZER_NODE } from '../../../../screens/alerts'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -19,13 +24,11 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Alert details expandable flyout left panel analyzer graph', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(getNewRule()); @@ -33,10 +36,20 @@ describe.skip( waitForAlertsToPopulate(); expandFirstAlertExpandableFlyout(); expandDocumentDetailsExpandableFlyoutLeftSection(); - openGraphAnalyzer(); + openGraphAnalyzerTab(); }); - it('should display analyzer graph and node list', () => { + it('should display analyzer graph and node list under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) + .should('be.visible') + .and('have.text', 'Visualize'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) + .should('be.visible') + .and('have.text', 'Analyzer Graph'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); cy.get(ANALYZER_NODE).first().should('be.visible'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts new file mode 100644 index 0000000000000..e780500ce67c4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_correlations_tab'; +import { + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { openCorrelationsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_correlations_tab'; +import { openInsightsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { cleanKibana } from '../../../../tasks/common'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { login, visit } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; + +describe( + 'Expandable flyout left panel correlations', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openCorrelationsTab(); + }); + + it('should serialize its state to url', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) + .should('be.visible') + .and('have.text', 'Insights'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) + .should('be.visible') + .and('have.text', 'Correlations'); + + // TODO actual test + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts index 71983473b7ac1..f48611f12af09 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts @@ -6,15 +6,18 @@ */ import { - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS, + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON, DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS, -} from '../../../../screens/document_expandable_flyout'; + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS, +} from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab'; import { - expandFirstAlertExpandableFlyout, - openInsightsTab, - openEntities, - expandDocumentDetailsExpandableFlyoutLeftSection, -} from '../../../../tasks/document_expandable_flyout'; + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { openEntitiesTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_entities_tab'; +import { openInsightsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -22,13 +25,11 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Alert details expandable flyout left panel entities', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(getNewRule()); @@ -37,18 +38,25 @@ describe.skip( expandFirstAlertExpandableFlyout(); expandDocumentDetailsExpandableFlyoutLeftSection(); openInsightsTab(); - openEntities(); + openEntitiesTab(); }); - it('should display analyzer graph and node list', () => { - // eslint-disable-next-line cypress/unsafe-to-chain-command - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS) - .scrollIntoView() - .should('be.visible'); - // eslint-disable-next-line cypress/unsafe-to-chain-command - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS) - .scrollIntoView() - .should('be.visible'); + it('should display analyzer graph and node list under Insights Entities', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) + .should('be.visible') + .and('have.text', 'Insights'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) + .should('be.visible') + .and('have.text', 'Entities'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); }); } ); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts new file mode 100644 index 0000000000000..62d4932a017b8 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB, + DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT, +} from '../../../../screens/expandable_flyout/alert_details_left_panel_investigation_tab'; +import { openInvestigationTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_investigation_tab'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { cleanKibana } from '../../../../tasks/common'; +import { login, visit } from '../../../../tasks/login'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; + +describe( + 'Alert details expandable flyout left panel investigation', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInvestigationTab(); + }); + + it('should display investigation guide', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) + .should('be.visible') + .and('have.text', 'Investigation'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts new file mode 100644 index 0000000000000..972937c4a73f4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_prevalence_tab'; +import { + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { openPrevalenceTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_prevalence_tab'; +import { openInsightsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { cleanKibana } from '../../../../tasks/common'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { login, visit } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; + +describe( + 'Expandable flyout left panel prevalence', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openPrevalenceTab(); + }); + + it('should show prevalence table', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) + .should('be.visible') + .and('have.text', 'Insights'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Prevalence'); + + // TODO actual test + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts index 31c410dff502e..762b5cf307b4f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts @@ -5,11 +5,16 @@ * 2.0. */ -import { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_NO_DATA } from '../../../../screens/document_expandable_flyout'; import { - expandFirstAlertExpandableFlyout, - expandDocumentDetailsExpandableFlyoutLeftSection, -} from '../../../../tasks/document_expandable_flyout'; + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON, + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR, +} from '../../../../screens/expandable_flyout/alert_details_left_panel_session_view_tab'; +import { + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB, + DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -17,13 +22,11 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Alert details expandable flyout left panel session view', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(getNewRule()); @@ -33,13 +36,22 @@ describe.skip( expandDocumentDetailsExpandableFlyoutLeftSection(); }); - it('should display session view no data message', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_NO_DATA) + it('should display session view under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) .should('be.visible') - .and('contain.text', 'No data to render') - .and('contain.text', 'No process events found for this query'); - }); + .and('have.text', 'Visualize'); - // it('should display session view component', () => {}); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Session View'); + + // TODO ideally we would have a test for the session view component instead + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR) + .should('be.visible') + .and('contain.text', 'Unable to display session view') + .and('contain.text', 'There was an error displaying session view'); + }); } ); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts index c1cddd0d61e61..d79c59ed71440 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts @@ -5,28 +5,51 @@ * 2.0. */ +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { INDICATOR_MATCH_ENRICHMENT_SECTION } from '../../../../screens/alerts_details'; import { cleanKibana } from '../../../../tasks/common'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -import { expandFirstAlertExpandableFlyout } from '../../../../tasks/document_expandable_flyout'; import { login, visit } from '../../../../tasks/login'; import { ALERTS_URL } from '../../../../urls/navigation'; +import { openInsightsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel'; +import { openThreatIntelligenceTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_threat_intelligence_tab'; +import { + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, + DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, +} from '../../../../screens/expandable_flyout/alert_details_left_panel'; +import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Expandable flyout left panel threat intelligence', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); + createRule(getNewRule()); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openThreatIntelligenceTab(); }); it('should serialize its state to url', () => { - cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) + .should('be.visible') + .and('have.text', 'Insights'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); + + cy.get(INDICATOR_MATCH_ENRICHMENT_SECTION).should('be.visible'); }); } ); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index cd02b424e4697..10780ca4e37f7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -5,26 +5,57 @@ * 2.0. */ +import { upperFirst } from 'lodash'; +import { + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, + EXISTING_CASE_SELECT_BUTTON, + VIEW_CASE_TOASTER_LINK, +} from '../../../../screens/expandable_flyout/common'; +import { + createNewCaseFromCases, + expandFirstAlertExpandableFlyout, + navigateToAlertsPage, + navigateToCasesPage, +} from '../../../../tasks/expandable_flyout/common'; +import { ALERT_CHECKBOX } from '../../../../screens/alerts'; +import { CASE_DETAILS_PAGE_TITLE } from '../../../../screens/case_details'; import { DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON, DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE, + DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE, + DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY, + DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION, + DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED, + DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND, + DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON, + DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE, + DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE, + DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY, + DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE, DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE, DOCUMENT_DETAILS_FLYOUT_JSON_TAB, - DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW, -} from '../../../../screens/document_expandable_flyout'; +} from '../../../../screens/expandable_flyout/alert_details_right_panel'; import { collapseDocumentDetailsExpandableFlyoutLeftSection, expandDocumentDetailsExpandableFlyoutLeftSection, - expandFirstAlertExpandableFlyout, openJsonTab, - openOverviewTab, openTableTab, - scrollWithinDocumentDetailsExpandableFlyoutRightSection, -} from '../../../../tasks/document_expandable_flyout'; + openTakeActionButton, + openTakeActionButtonAndSelectItem, + selectTakeActionItem, +} from '../../../../tasks/expandable_flyout/alert_details_right_panel'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -32,28 +63,47 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Alert details expandable flyout right panel', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { const rule = getNewRule(); - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(rule); visit(ALERTS_URL); waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); }); - it('should display title in the header', () => { + it('should display header and footer basics', () => { + expandFirstAlertExpandableFlyout(); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); - it('should toggle expand detail button in the header', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) + .should('be.visible') + .and('have.text', rule.risk_score); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) + .should('be.visible') + .and('have.text', upperFirst(rule.severity)); + + cy.log('Verify all 3 tabs are visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) + .should('be.visible') + .and('have.text', 'Overview'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + + cy.log('Verify the expand/collapse button is visible and functionality works'); + expandDocumentDetailsExpandableFlyoutLeftSection(); cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) .should('be.visible') @@ -63,34 +113,121 @@ describe.skip( cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) .should('be.visible') .and('have.text', 'Expand alert details'); + + cy.log('Verify the take action button is visible on all tabs'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); + + openTableTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); + + openJsonTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); }); - it('should display 3 tabs in the right section', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) - .should('be.visible') - .and('have.text', 'Overview'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + // TODO this will change when add to existing case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to existing case', () => { + navigateToCasesPage(); + createNewCaseFromCases(); + + cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); + navigateToAlertsPage(); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); + + cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); }); - it('should display tab content when switching tabs in the right section', () => { - openOverviewTab(); - // we shouldn't need to test anything here as it's covered with the new overview_tab file + // TODO this will change when add to new case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to new case', () => { + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); + + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( + 'case description' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); - openTableTab(); - // the table component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that scrolls to a specific element in the table to ensure Cypress finds it - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); + }); - // scroll back up to the top to open the json tab - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).scrollIntoView(); + it('should mark as acknowledged', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); - openJsonTab(); - // the json component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that vertically scrolls down to ensure Cypress finds it - scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 6500); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); + + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST) + // .should('be.visible') + // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); + + it('should mark as closed', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); + + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); + + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); + + // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown + it('should test other action within take action dropdown', () => { + expandFirstAlertExpandableFlyout(); + + cy.log('should add endpoint exception'); + + // TODO figure out why this option is disabled in Cypress but not running the app locally + // https://github.com/elastic/security-team/issues/6300 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + + cy.log('should add rule exception'); + + // TODO this isn't fully testing the add rule exception yet + // https://github.com/elastic/security-team/issues/6301 + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) + .should('be.visible') + .click(); + + // cy.log('should isolate host'); + + // TODO figure out why isolate host isn't showing up in the dropdown + // https://github.com/elastic/security-team/issues/6302 + // openTakeActionButton(); + // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + + cy.log('should respond'); + + // TODO this will change when respond is improved + // https://github.com/elastic/security-team/issues/6303 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); + + cy.log('should investigate in timeline'); + + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) + .first() + .within(() => + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') + ); }); } ); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts deleted file mode 100644 index 8cf2b0c2a532e..0000000000000 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* eslint-disable cypress/unsafe-to-chain-command */ - -import { - CASE_ACTION_WRAPPER, - CASE_ELLIPSE_BUTTON, - CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON, - CASE_ELLIPSE_DELETE_CASE_OPTION, - CREATE_CASE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_FOOTER, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, - DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE, - DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY, - DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION, - DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED, - DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND, - DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON, - EXISTING_CASE_SELECT_BUTTON, - KIBANA_TOAST, - NEW_CASE_CREATE_BUTTON, - NEW_CASE_DESCRIPTION_INPUT, - NEW_CASE_NAME_INPUT, - VIEW_CASE_TOASTER_LINK, -} from '../../../../screens/document_expandable_flyout'; -import { - expandFirstAlertExpandableFlyout, - navigateToAlertsPage, - navigateToCasesPage, - openJsonTab, - openOverviewTab, - openTableTab, - openTakeActionButton, - openTakeActionButtonAndSelectItem, -} from '../../../../tasks/document_expandable_flyout'; -import { cleanKibana } from '../../../../tasks/common'; -import { login, visit } from '../../../../tasks/login'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { getNewRule } from '../../../../objects/rule'; -import { ALERTS_URL } from '../../../../urls/navigation'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; - -const createNewCaseFromCases = () => { - navigateToCasesPage(); - cy.get(CREATE_CASE_BUTTON).should('be.visible').click(); - cy.get(NEW_CASE_NAME_INPUT).should('be.visible').click().type('case'); - cy.get(NEW_CASE_DESCRIPTION_INPUT).should('be.visible').click().type('case description'); - cy.get(NEW_CASE_CREATE_BUTTON).should('be.visible').click(); -}; - -const deleteCase = () => { - cy.get(CASE_ACTION_WRAPPER).find(CASE_ELLIPSE_BUTTON).should('be.visible').click(); - cy.get(CASE_ELLIPSE_DELETE_CASE_OPTION).should('be.visible').click(); - cy.get(CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON).should('be.visible').click(); -}; - -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( - 'Alert details expandable flyout right panel footer', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - before(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); - - it('should display footer take action button on all tabs', () => { - openOverviewTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView().should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openTableTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView().should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openJsonTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView().should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - // reset state for next test - openOverviewTab(); - }); - - // TODO this will change when add to existing case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to existing case', () => { - createNewCaseFromCases(); - - navigateToAlertsPage(); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); - - cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); - cy.get(VIEW_CASE_TOASTER_LINK).click(); - deleteCase(); - - // navigate back to alert page and reopen flyout for next test - navigateToAlertsPage(); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); - - // TODO this will change when add to new case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to new case', () => { - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( - 'case description' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); - - cy.get(VIEW_CASE_TOASTER_LINK).click(); - deleteCase(); - - // navigate back to alert page and reopen flyout for next test - navigateToAlertsPage(); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); - - // TODO figure out how to properly test the result and recreate the alert for the next tests - it.skip('should mark as acknowledged', () => { - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); - cy.get(KIBANA_TOAST) - // .should('be.visible') - .and('have.text', 'Successfully marked 1 alert as acknowledged.'); - }); - - // TODO figure out how to properly test the result and recreate the alert for the next tests - it.skip('should mark as closed', () => { - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); - cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); - }); - - // TODO figure out why this option is disabled in Cypress but not running the app locally - // https://github.com/elastic/security-team/issues/6300 - it('should add endpoint exception', () => { - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); - }); - - // TODO this isn't fully testing the add rule exception yet - // https://github.com/elastic/security-team/issues/6301 - it('should add rule exception', () => { - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) - .should('be.visible') - .click(); - }); - - // TODO figure out why isolate host isn't showing up in the dropdown - // https://github.com/elastic/security-team/issues/6302 - // it.skip('should isolate host', () => {}); - - // TODO this will change when respond is improved - // https://github.com/elastic/security-team/issues/6303 - it('should respond', () => { - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); - }); - - it('should investigate in timeline', () => { - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) - .first() - .within(() => - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') - ); - }); - } -); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts deleted file mode 100644 index 13dbf7c163419..0000000000000 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts +++ /dev/null @@ -1,61 +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 { upperFirst } from 'lodash'; -import { - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE, -} from '../../../../screens/document_expandable_flyout'; -import { expandFirstAlertExpandableFlyout } from '../../../../tasks/document_expandable_flyout'; -import { cleanKibana } from '../../../../tasks/common'; -import { login, visit } from '../../../../tasks/login'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { getNewRule } from '../../../../objects/rule'; -import { ALERTS_URL } from '../../../../urls/navigation'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; - -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( - 'Alert details expandable flyout right panel header', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); - - before(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); - - it('should display correct title in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE) - .should('be.visible') - .and('have.text', rule.name); - }); - - it('should display risk score in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE) - .should('be.visible') - .and('have.text', rule.risk_score); - }); - - it('should display severity in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE) - .should('be.visible') - .and('have.text', upperFirst(rule.severity)); - }); - } -); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts new file mode 100644 index 0000000000000..7bd86e509ac18 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.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 { scrollWithinDocumentDetailsExpandableFlyoutRightSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel_json_tab'; +import { openJsonTab } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT } from '../../../../screens/expandable_flyout/alert_details_right_panel_json_tab'; +import { cleanKibana } from '../../../../tasks/common'; +import { login, visit } from '../../../../tasks/login'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; + +describe( + 'Alert details expandable flyout right panel json tab', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openJsonTab(); + }); + + it('should display the json component', () => { + // the json component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that vertically scrolls down to ensure Cypress finds it + scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 7000); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 6e81a34de222a..e344e1bd6afbf 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -5,90 +5,97 @@ * 2.0. */ -/* eslint-disable cypress/unsafe-to-chain-command */ - +import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE } from '../../../../screens/expandable_flyout/alert_details_right_panel'; +import { DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT } from '../../../../screens/expandable_flyout/alert_details_left_panel_investigation_tab'; import { - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, +} from '../../../../screens/expandable_flyout/common'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES, -} from '../../../../screens/document_expandable_flyout'; + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE, +} from '../../../../screens/expandable_flyout/alert_details_right_panel_overview_tab'; import { - expandFirstAlertExpandableFlyout, - openOverviewTab, + clickCorrelationsViewAllButton, + clickEntitiesViewAllButton, + clickInvestigationGuideButton, + clickPrevalenceViewAllButton, + clickThreatIntelligenceViewAllButton, toggleOverviewTabDescriptionSection, - toggleOverviewTabInvestigationSection, toggleOverviewTabInsightsSection, + toggleOverviewTabInvestigationSection, toggleOverviewTabVisualizationsSection, - clickThreatIntelligenceViewAllButton, - clickPrevalenceViewAllButton, -} from '../../../../tasks/document_expandable_flyout'; +} from '../../../../tasks/expandable_flyout/alert_details_right_panel_overview_tab'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { openTakeActionButtonAndSelectItem } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW, +} from '../../../../screens/expandable_flyout/alert_details_right_panel_table_tab'; +import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT } from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +describe( 'Alert details expandable flyout right panel overview tab', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { const rule = getNewRule(); - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(rule); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlertExpandableFlyout(); - openOverviewTab(); }); describe('description section', () => { - it('should display description section header and content', () => { + it('should display description section', () => { + cy.log('header and content'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER) .should('be.visible') .and('have.text', 'Description'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT).should( 'be.visible' ); - }); - it('should display document description and expand button', () => { + cy.log('expand button'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) .should('be.visible') .and('have.text', 'Rule description'); @@ -103,18 +110,18 @@ describe.skip( 'have.text', 'Collapse' ); - }); - it('should display reason', () => { + cy.log('reason'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) .should('be.visible') .and('have.text', 'Alert reason'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) .should('be.visible') .and('contain.text', rule.name); - }); - it('should display mitre attack', () => { + cy.log('mitre attack'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) .should('be.visible') // @ts-ignore @@ -130,36 +137,42 @@ describe.skip( }); describe('investigation section', () => { - before(() => { + it('should display investigation section', () => { toggleOverviewTabDescriptionSection(); toggleOverviewTabInvestigationSection(); - }); - it('should display description section header and content', () => { + cy.log('header and content'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) .should('be.visible') .and('have.text', 'Investigation'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT).should( 'be.visible' ); - }); - it('should display investigation guide button', () => { + cy.log('investigation guide button'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) .should('be.visible') .and('have.text', 'Investigation guide'); - }); - it('should display highlighted fields', () => { + cy.log('should navigate to left Investigation tab'); + + clickInvestigationGuideButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + + cy.log('highlighted fields'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) .should('be.visible') .and('have.text', 'Highlighted fields'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should( 'be.visible' ); - }); - it('should navigate to table tab when clicking on highlighted fields view button', () => { + cy.log('navigate to table tab when clicking on highlighted fields view button'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK) .should('be.visible') .click(); @@ -169,21 +182,18 @@ describe.skip( // (in the middle of it vertically) to ensure Cypress finds it cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); - - // go back to Overview tab to reset view for next test - openOverviewTab(); }); }); describe('insights section', () => { - before(() => { + it('should display entities section', () => { toggleOverviewTabDescriptionSection(); toggleOverviewTabInsightsSection(); - }); - it('should display entities section', () => { + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER) - .scrollIntoView() .should('be.visible') .and('have.text', 'Entities'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT).should('be.visible'); @@ -193,21 +203,28 @@ describe.skip( cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT).should( 'be.visible' ); - }); - it('should navigate to left panel, entities tab when view all entities is clicked', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON) - .should('be.visible') - .click(); + cy.log('should navigate to left panel Entities tab'); + + clickEntitiesViewAllButton(); cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); }); - // TODO work on getting proper IoC data to make the threat intelligence section work here - it.skip('should display threat intelligence section', () => { + it('should display threat intelligence section', () => { + toggleOverviewTabDescriptionSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER + ).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER) - .scrollIntoView() .should('be.visible') .and('have.text', 'Threat Intelligence'); + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT + ).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT) .should('be.visible') .within(() => { @@ -215,70 +232,81 @@ describe.skip( cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) .eq(0) .should('be.visible') - .and('have.text', '1 threat match detected'); // TODO + .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here // field with threat enrichement cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) .eq(1) .should('be.visible') - .and('have.text', '1 field enriched with threat intelligence'); // TODO + .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here }); - }); - // TODO work on getting proper IoC data to make the threat intelligence section work here - // and improve when we can navigate Threat Intelligence to sub tab directly - it.skip('should navigate to left panel, entities tab when view all fields of threat intelligence is clicked', () => { + cy.log('should navigate to left panel Threat Intelligence tab'); + clickThreatIntelligenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Threat Intelligence sub tab directly }); - // TODO work on getting proper data to display in the cases, ancestry, session and source event sections - it.skip('should display correlations section', () => { + it('should display correlations section', () => { + cy.log('link the alert to a new case'); + + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( + 'case description' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); + + toggleOverviewTabDescriptionSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER) - .scrollIntoView() .should('be.visible') .and('have.text', 'Correlations'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT) .should('be.visible') .within(() => { - // threat match detected cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) .eq(0) .should('be.visible') - .and('have.text', '1 related case'); // TODO - - // field with threat enrichement + .and('have.text', '1 related case'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) .eq(1) .should('be.visible') - .and('have.text', '1 alert related by ancestry'); // TODO + .and('have.text', '1 alert related by ancestry'); + // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + // .eq(2) + // .should('be.visible') + // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(1) + .eq(2) .should('be.visible') - .and('have.text', '1 alert related by the same source event'); // TODO - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(1) - .should('be.visible') - .and('have.text', '1 alert related by session'); // TODO + .and('have.text', '1 alert related by session'); }); - }); - // TODO work on getting proper data to display in the cases, ancestry, session and source event sections - // and improve when we can navigate Correlations to sub tab directly - it.skip('should navigate to left panel, entities tab when view all fields of correlations is clicked', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON) - .should('be.visible') - .click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + cy.log('should navigate to left panel Correlations tab'); + + clickCorrelationsViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Correlations sub tab directly }); // TODO work on getting proper data to make the prevalence section work here // we need to generate enough data to have at least one field with prevalence it.skip('should display prevalence section', () => { + toggleOverviewTabDescriptionSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER) - .scrollIntoView() .should('be.visible') .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT) .should('be.visible') .within(() => { @@ -286,23 +314,19 @@ describe.skip( .should('be.visible') .and('have.text', 'is uncommon'); }); - }); - // TODO work on getting proper data to make the prevalence section work here - // we need to generate enough data to have at least one field with prevalence - it.skip('should navigate to left panel, entities tab when view all fields of prevalence is clicked', () => { + cy.log('should navigate to left panel Prevalence tab'); + clickPrevalenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Prevalence sub tab directly }); }); describe('visualizations section', () => { - before(() => { - toggleOverviewTabInsightsSection(); - toggleOverviewTabVisualizationsSection(); - }); - it('should display analyzer preview', () => { + toggleOverviewTabDescriptionSection(); + toggleOverviewTabVisualizationsSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).should('be.visible'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts index 2f50e234b9add..9e30ba52b3cdd 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { openTableTab } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; import { closeTimeline, openActiveTimeline } from '../../../../tasks/timeline'; import { PROVIDER_BADGE } from '../../../../screens/timeline'; import { removeKqlFilter } from '../../../../tasks/search_bar'; @@ -13,17 +15,15 @@ import { DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW, -} from '../../../../screens/document_expandable_flyout'; +} from '../../../../screens/expandable_flyout/alert_details_right_panel_table_tab'; import { addToTimelineTableTabTable, clearFilterTableTabTable, copyToClipboardTableTabTable, - expandFirstAlertExpandableFlyout, filterInTableTabTable, filterOutTableTabTable, filterTableTabTable, - openTableTab, -} from '../../../../tasks/document_expandable_flyout'; +} from '../../../../tasks/expandable_flyout/alert_details_right_panel_table_tab'; import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -31,13 +31,11 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( - 'Alert details expandable flyout right panel', +describe( + 'Alert details expandable flyout right panel table tab', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(getNewRule()); @@ -55,26 +53,28 @@ describe.skip( clearFilterTableTabTable(); }); - it('should test filter in cell actions', () => { + it('should test cell actions', () => { + cy.log('cell actions filter in'); + filterInTableTabTable(); cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); removeKqlFilter(); - }); - it('should test filter out cell actions', () => { + cy.log('cell actions filter out'); + filterOutTableTabTable(); cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); removeKqlFilter(); - }); - it('should test add to timeline cell actions', () => { + cy.log('cell actions add to timeline'); + addToTimelineTableTabTable(); openActiveTimeline(); cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); closeTimeline(); - }); - it('should test copy to clipboard cell actions', () => { + cy.log('cell actions copy to clipboard'); + copyToClipboardTableTabTable(); cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts index 33683a86b8227..fa268bfdfa341 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts @@ -8,50 +8,48 @@ import { getNewRule } from '../../../../objects/rule'; import { cleanKibana } from '../../../../tasks/common'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -import { expandFirstAlertExpandableFlyout } from '../../../../tasks/document_expandable_flyout'; import { login, visit } from '../../../../tasks/login'; import { createRule } from '../../../../tasks/api_calls/rules'; import { ALERTS_URL } from '../../../../urls/navigation'; -import { - DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE, -} from '../../../../screens/document_expandable_flyout'; - -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip( +import { closeFlyout } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE } from '../../../../screens/expandable_flyout/alert_details_right_panel'; + +describe( 'Expandable flyout state sync', { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { const rule = getNewRule(); - before(() => { + beforeEach(() => { cleanKibana(); login(); createRule(rule); visit(ALERTS_URL); waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); }); - it('should serialize its state to url', () => { - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); + it('should test flyout url sync', () => { + cy.url().should('not.include', 'eventFlyout'); - it('should reopen the flyout after browser refresh', () => { - cy.reload(); + expandFirstAlertExpandableFlyout(); + + cy.log('should serialize its state to url'); cy.url().should('include', 'eventFlyout'); cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); - it('should clear the url state when flyout is closed', () => { + cy.log('should reopen the flyout after browser refresh'); + cy.reload(); + waitForAlertsToPopulate(); + cy.url().should('include', 'eventFlyout'); cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - cy.get(DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON).click(); + cy.log('should clear the url state when flyout is closed'); + + closeFlyout(); cy.url().should('not.include', 'eventFlyout'); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts deleted file mode 100644 index 680b1fd4c7eb4..0000000000000 --- a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts +++ /dev/null @@ -1,386 +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 { - ANALYZER_GRAPH_TEST_ID, - SESSION_VIEW_TEST_ID, - ENTITIES_DETAILS_TEST_ID, - THREAT_INTELLIGENCE_DETAILS_TEST_ID, - PREVALENCE_DETAILS_TEST_ID, - CORRELATIONS_DETAILS_TEST_ID, - USER_DETAILS_TEST_ID, - HOST_DETAILS_TEST_ID, -} from '../../public/flyout/left/components/test_ids'; -import { - HISTORY_TAB_CONTENT_TEST_ID, - INVESTIGATION_TAB_CONTENT_TEST_ID, - INSIGHTS_TAB_BUTTON_GROUP_TEST_ID, - INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID, - INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID, - INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID, - INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID, - VISUALIZE_TAB_BUTTON_GROUP_TEST_ID, - VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID, - VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID, -} from '../../public/flyout/left/tabs/test_ids'; -import { - HISTORY_TAB_TEST_ID, - INSIGHTS_TAB_TEST_ID, - INVESTIGATION_TAB_TEST_ID, - VISUALIZE_TAB_TEST_ID, -} from '../../public/flyout/left/test_ids'; -import { - FLYOUT_BODY_TEST_ID, - JSON_TAB_TEST_ID, - OVERVIEW_TAB_TEST_ID, - TABLE_TAB_TEST_ID, -} from '../../public/flyout/right/test_ids'; -import { - JSON_TAB_CONTENT_TEST_ID, - TABLE_TAB_CONTENT_TEST_ID, -} from '../../public/flyout/right/tabs/test_ids'; -import { - COLLAPSE_DETAILS_BUTTON_TEST_ID, - DESCRIPTION_DETAILS_TEST_ID, - DESCRIPTION_EXPAND_BUTTON_TEST_ID, - DESCRIPTION_SECTION_CONTENT_TEST_ID, - DESCRIPTION_SECTION_HEADER_TEST_ID, - DESCRIPTION_TITLE_TEST_ID, - EXPAND_DETAILS_BUTTON_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID, - FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID, - FLYOUT_HEADER_TITLE_TEST_ID, - HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, - HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK, - HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID, - HIGHLIGHTED_FIELDS_TEST_ID, - HIGHLIGHTED_FIELDS_TITLE_TEST_ID, - INVESTIGATION_SECTION_CONTENT_TEST_ID, - INVESTIGATION_SECTION_HEADER_TEST_ID, - MITRE_ATTACK_DETAILS_TEST_ID, - MITRE_ATTACK_TITLE_TEST_ID, - REASON_DETAILS_TEST_ID, - REASON_TITLE_TEST_ID, - INSIGHTS_HEADER_TEST_ID, - ENTITIES_HEADER_TEST_ID, - ENTITIES_CONTENT_TEST_ID, - ENTITY_PANEL_HEADER_TEST_ID, - ENTITY_PANEL_CONTENT_TEST_ID, - ENTITIES_VIEW_ALL_BUTTON_TEST_ID, - VISUALIZATIONS_SECTION_HEADER_TEST_ID, - ANALYZER_TREE_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID, - INVESTIGATION_GUIDE_BUTTON_TEST_ID, - INSIGHTS_CORRELATIONS_TITLE_TEST_ID, - INSIGHTS_CORRELATIONS_CONTENT_TEST_ID, - INSIGHTS_CORRELATIONS_VALUE_TEST_ID, - INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON_TEST_ID, - INSIGHTS_PREVALENCE_TITLE_TEST_ID, - INSIGHTS_PREVALENCE_CONTENT_TEST_ID, - INSIGHTS_PREVALENCE_VALUE_TEST_ID, - INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON_TEST_ID, -} from '../../public/flyout/right/components/test_ids'; -import { - getClassSelector, - getDataTestSubjectSelector, - getDataTestSubjectSelectorStartWith, -} from '../helpers/common'; - -/* Kibana */ - -export const KIBANA_NAVBAR_ALERTS_PAGE = getDataTestSubjectSelector( - 'solutionSideNavItemLink-alerts' -); -export const KIBANA_NAVBAR_CASES_PAGE = getDataTestSubjectSelector('solutionSideNavItemLink-cases'); -export const KIBANA_TOAST = getDataTestSubjectSelector('globalToastList'); - -/* Right section */ - -export const DOCUMENT_DETAILS_FLYOUT_BODY = getDataTestSubjectSelector(FLYOUT_BODY_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE = getDataTestSubjectSelector( - FLYOUT_HEADER_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON = getDataTestSubjectSelector( - EXPAND_DETAILS_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON = getDataTestSubjectSelector( - COLLAPSE_DETAILS_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB = - getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT = - getDataTestSubjectSelector(TABLE_TAB_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT = - getDataTestSubjectSelector(JSON_TAB_CONTENT_TEST_ID); - -/* Left section */ - -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB = - getDataTestSubjectSelector(VISUALIZE_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB = - getDataTestSubjectSelector(INSIGHTS_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB = - getDataTestSubjectSelector(INVESTIGATION_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB = getDataTestSubjectSelector(HISTORY_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT = getDataTestSubjectSelector( - INVESTIGATION_TAB_CONTENT_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT = getDataTestSubjectSelector( - HISTORY_TAB_CONTENT_TEST_ID -); - -/* Left Section - Visualize tab */ -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP = getDataTestSubjectSelector( - VISUALIZE_TAB_BUTTON_GROUP_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON = getDataTestSubjectSelector( - VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT = - getDataTestSubjectSelector(SESSION_VIEW_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_NO_DATA = - getDataTestSubjectSelector('sessionView:sessionViewProcessEventsEmpty'); -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON = - getDataTestSubjectSelector(VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT = - getDataTestSubjectSelector(ANALYZER_GRAPH_TEST_ID); - -/* Left Section - Insights tab */ -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP = getDataTestSubjectSelector( - INSIGHTS_TAB_BUTTON_GROUP_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON = getDataTestSubjectSelector( - INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT = - getDataTestSubjectSelector(ENTITIES_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS = - getDataTestSubjectSelector(USER_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS = - getDataTestSubjectSelector(HOST_DETAILS_TEST_ID); - -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON = - getDataTestSubjectSelector(INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_CONTENT = - getDataTestSubjectSelector(THREAT_INTELLIGENCE_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON = getDataTestSubjectSelector( - INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_CONTENT = getDataTestSubjectSelector( - PREVALENCE_DETAILS_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON = getDataTestSubjectSelector( - INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_CONTENT = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_TEST_ID -); - -/* Footer */ - -export const DOCUMENT_DETAILS_FLYOUT_FOOTER = getDataTestSubjectSelector( - 'side-panel-flyout-footer' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON = getDataTestSubjectSelector( - 'take-action-dropdown-btn' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN = - getDataTestSubjectSelector('takeActionPanelMenu'); - -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE = getDataTestSubjectSelector( - 'add-to-existing-case-action' -); -export const CREATE_CASE_BUTTON = `[data-test-subj="createNewCaseBtn"]`; -export const NEW_CASE_NAME_INPUT = `[data-test-subj="input"][aria-describedby="caseTitle"]`; - -export const NEW_CASE_DESCRIPTION_INPUT = getDataTestSubjectSelector('euiMarkdownEditorTextArea'); - -export const NEW_CASE_CREATE_BUTTON = getDataTestSubjectSelector('create-case-submit'); -export const EXISTING_CASE_SELECT_BUTTON = - getDataTestSubjectSelectorStartWith('cases-table-row-select-'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE = - getDataTestSubjectSelector('add-to-new-case-action'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT = NEW_CASE_NAME_INPUT; -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT = - NEW_CASE_DESCRIPTION_INPUT; - -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON = NEW_CASE_CREATE_BUTTON; - -export const VIEW_CASE_TOASTER_LINK = getDataTestSubjectSelector('toaster-content-case-view-link'); -export const CASE_ACTION_WRAPPER = getDataTestSubjectSelector('case-action-bar-wrapper'); - -export const CASE_ELLIPSE_BUTTON = getDataTestSubjectSelector('property-actions-case-ellipses'); - -export const CASE_ELLIPSE_DELETE_CASE_OPTION = getDataTestSubjectSelector( - 'property-actions-case-trash' -); - -export const CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON = getDataTestSubjectSelector( - 'confirmModalConfirmButton' -); - -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED = getDataTestSubjectSelector( - 'acknowledged-alert-status' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED = - getDataTestSubjectSelector('close-alert-status'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION = getDataTestSubjectSelector( - 'add-endpoint-exception-menu-item' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION = - getDataTestSubjectSelector('add-exception-menu-item'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER = - getDataTestSubjectSelector('exceptionFlyoutTitle'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON = - getDataTestSubjectSelector('cancelExceptionAddButton'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND = getDataTestSubjectSelector( - 'endpointResponseActions-action-item' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE = getDataTestSubjectSelector( - 'investigate-in-timeline-action-item' -); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION = - getDataTestSubjectSelector('timelineHeader'); -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY = - getDataTestSubjectSelector('providerContainer'); - -/* Overview tab */ - -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER = - getDataTestSubjectSelector(DESCRIPTION_SECTION_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT = - getDataTestSubjectSelector(DESCRIPTION_SECTION_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE = - getDataTestSubjectSelector(DESCRIPTION_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS = getDataTestSubjectSelector( - DESCRIPTION_DETAILS_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON = - getDataTestSubjectSelector(DESCRIPTION_EXPAND_BUTTON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE = - getDataTestSubjectSelector(REASON_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS = - getDataTestSubjectSelector(REASON_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE = getDataTestSubjectSelector( - MITRE_ATTACK_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS = getDataTestSubjectSelector( - MITRE_ATTACK_DETAILS_TEST_ID -); -export const DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE = getDataTestSubjectSelector( - FLYOUT_HEADER_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE = getDataTestSubjectSelector( - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE = getDataTestSubjectSelector( - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID -); -export const DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY = getDataTestSubjectSelector( - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE = getDataTestSubjectSelector( - FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS = getDataTestSubjectSelector( - HIGHLIGHTED_FIELDS_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON = - getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER = - getDataTestSubjectSelector(INVESTIGATION_SECTION_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT = - getDataTestSubjectSelector(INVESTIGATION_SECTION_CONTENT_TEST_ID); - -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE = - getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS = - getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK = - getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON = - getDataTestSubjectSelector(INVESTIGATION_GUIDE_BUTTON_TEST_ID); - -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER = - getDataTestSubjectSelector(INSIGHTS_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER = - getDataTestSubjectSelector(ENTITIES_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT = - getDataTestSubjectSelector(ENTITIES_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON = - getDataTestSubjectSelector(ENTITIES_VIEW_ALL_BUTTON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER = - getDataTestSubjectSelector(ENTITY_PANEL_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT = - getDataTestSubjectSelector(ENTITY_PANEL_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_VALUE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON_TEST_ID); - -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_TITLE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_VALUE_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON_TEST_ID); - -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER = - getDataTestSubjectSelector(VISUALIZATIONS_SECTION_HEADER_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE = - getDataTestSubjectSelector(ANALYZER_TREE_TEST_ID); - -/* Table tab */ - -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER = getClassSelector('euiFieldSearch'); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER = - getDataTestSubjectSelector('clearSearchButton'); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-@timestamp' -); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-_id' -); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-event.type' -); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN = getDataTestSubjectSelector( - 'actionItem-security-detailsFlyout-cellActions-filterIn' -); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT = getDataTestSubjectSelector( - 'actionItem-security-detailsFlyout-cellActions-filterOut' -); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS = - getDataTestSubjectSelector('showExtraActionsButton'); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE = - getDataTestSubjectSelector('actionItem-security-detailsFlyout-cellActions-addToTimeline'); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD = - getDataTestSubjectSelector('actionItem-security-detailsFlyout-cellActions-copyToClipboard'); - -export const DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON = - getDataTestSubjectSelector('euiFlyoutCloseButton'); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel.ts new file mode 100644 index 0000000000000..f62d5848270c2 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel.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 { + INSIGHTS_TAB_BUTTON_GROUP_TEST_ID, + VISUALIZE_TAB_BUTTON_GROUP_TEST_ID, +} from '../../../public/flyout/left/tabs/test_ids'; +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { INSIGHTS_TAB_TEST_ID, VISUALIZE_TAB_TEST_ID } from '../../../public/flyout/left/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB = + getDataTestSubjectSelector(INSIGHTS_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB = + getDataTestSubjectSelector(VISUALIZE_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP = getDataTestSubjectSelector( + VISUALIZE_TAB_BUTTON_GROUP_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP = getDataTestSubjectSelector( + INSIGHTS_TAB_BUTTON_GROUP_TEST_ID +); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.ts new file mode 100644 index 0000000000000..cb35dff64b9be --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.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 { getDataTestSubjectSelector } from '../../helpers/common'; +import { VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; +import { ANALYZER_GRAPH_TEST_ID } from '../../../public/flyout/left/components/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON = + getDataTestSubjectSelector(VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT = + getDataTestSubjectSelector(ANALYZER_GRAPH_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts new file mode 100644 index 0000000000000..1cf076203c4fe --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.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 { getDataTestSubjectSelector } from '../../helpers/common'; +import { INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON = getDataTestSubjectSelector( + INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID +); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts new file mode 100644 index 0000000000000..32620b112bb83 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.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 { + ENTITIES_DETAILS_TEST_ID, + HOST_DETAILS_TEST_ID, + USER_DETAILS_TEST_ID, +} from '../../../public/flyout/left/components/test_ids'; +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON = getDataTestSubjectSelector( + INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID +); + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT = + getDataTestSubjectSelector(ENTITIES_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS = + getDataTestSubjectSelector(USER_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS = + getDataTestSubjectSelector(HOST_DETAILS_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_investigation_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_investigation_tab.ts new file mode 100644 index 0000000000000..084d2dc63b013 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_investigation_tab.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 { getDataTestSubjectSelector } from '../../helpers/common'; +import { INVESTIGATION_TAB_TEST_ID } from '../../../public/flyout/left/test_ids'; +import { INVESTIGATION_TAB_CONTENT_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB = + getDataTestSubjectSelector(INVESTIGATION_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT = getDataTestSubjectSelector( + INVESTIGATION_TAB_CONTENT_TEST_ID +); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_prevalence_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_prevalence_tab.ts new file mode 100644 index 0000000000000..d682214d4f9f5 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_prevalence_tab.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 { getDataTestSubjectSelector } from '../../helpers/common'; +import { INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON = getDataTestSubjectSelector( + INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID +); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts new file mode 100644 index 0000000000000..b382e85e47174 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; +import { SESSION_VIEW_ERROR_TEST_ID } from '../../../public/flyout/left/components/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON = getDataTestSubjectSelector( + VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR = getDataTestSubjectSelector( + SESSION_VIEW_ERROR_TEST_ID +); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.ts new file mode 100644 index 0000000000000..ab7275d50ea57 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.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. + */ + +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID } from '../../../public/flyout/left/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON = + getDataTestSubjectSelector(INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel.ts new file mode 100644 index 0000000000000..4a7b1a410b4a7 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel.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 { getDataTestSubjectSelector } from '../../helpers/common'; +import { + FLYOUT_BODY_TEST_ID, + JSON_TAB_TEST_ID, + OVERVIEW_TAB_TEST_ID, + TABLE_TAB_TEST_ID, +} from '../../../public/flyout/right/test_ids'; +import { + COLLAPSE_DETAILS_BUTTON_TEST_ID, + EXPAND_DETAILS_BUTTON_TEST_ID, + FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, + FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, + FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID, + FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID, + FLYOUT_HEADER_TITLE_TEST_ID, +} from '../../../public/flyout/right/components/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_BODY = getDataTestSubjectSelector(FLYOUT_BODY_TEST_ID); + +/* Header */ + +export const DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE = getDataTestSubjectSelector( + FLYOUT_HEADER_TITLE_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON = + getDataTestSubjectSelector('euiFlyoutCloseButton'); +export const DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON = getDataTestSubjectSelector( + EXPAND_DETAILS_BUTTON_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON = getDataTestSubjectSelector( + COLLAPSE_DETAILS_BUTTON_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB = + getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE = getDataTestSubjectSelector( + FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE = getDataTestSubjectSelector( + FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY = getDataTestSubjectSelector( + FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE = getDataTestSubjectSelector( + FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID +); + +/* Footer */ + +export const DOCUMENT_DETAILS_FLYOUT_FOOTER = getDataTestSubjectSelector( + 'side-panel-flyout-footer' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON = getDataTestSubjectSelector( + 'take-action-dropdown-btn' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN = + getDataTestSubjectSelector('takeActionPanelMenu'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE = + getDataTestSubjectSelector('add-to-new-case-action'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE = getDataTestSubjectSelector( + 'add-to-existing-case-action' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED = getDataTestSubjectSelector( + 'acknowledged-alert-status' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED = + getDataTestSubjectSelector('close-alert-status'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION = getDataTestSubjectSelector( + 'add-endpoint-exception-menu-item' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION = + getDataTestSubjectSelector('add-exception-menu-item'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER = + getDataTestSubjectSelector('exceptionFlyoutTitle'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON = + getDataTestSubjectSelector('cancelExceptionAddButton'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST = getDataTestSubjectSelector( + 'isolate-host-action-item' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND = getDataTestSubjectSelector( + 'endpointResponseActions-action-item' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE = getDataTestSubjectSelector( + 'investigate-in-timeline-action-item' +); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION = + getDataTestSubjectSelector('timelineHeader'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY = + getDataTestSubjectSelector('providerContainer'); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_json_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_json_tab.ts new file mode 100644 index 0000000000000..e7de5c5114250 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_json_tab.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. + */ + +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { JSON_TAB_CONTENT_TEST_ID } from '../../../public/flyout/right/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT = + getDataTestSubjectSelector(JSON_TAB_CONTENT_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts new file mode 100644 index 0000000000000..57ac333777b83 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getDataTestSubjectSelector } from '../../helpers/common'; +import { + ANALYZER_TREE_TEST_ID, + DESCRIPTION_DETAILS_TEST_ID, + DESCRIPTION_EXPAND_BUTTON_TEST_ID, + DESCRIPTION_SECTION_CONTENT_TEST_ID, + DESCRIPTION_SECTION_HEADER_TEST_ID, + DESCRIPTION_TITLE_TEST_ID, + ENTITIES_CONTENT_TEST_ID, + ENTITIES_HEADER_TEST_ID, + ENTITIES_VIEW_ALL_BUTTON_TEST_ID, + ENTITY_PANEL_CONTENT_TEST_ID, + ENTITY_PANEL_HEADER_TEST_ID, + HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, + HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK, + HIGHLIGHTED_FIELDS_TITLE_TEST_ID, + INSIGHTS_CORRELATIONS_CONTENT_TEST_ID, + INSIGHTS_CORRELATIONS_TITLE_TEST_ID, + INSIGHTS_CORRELATIONS_VALUE_TEST_ID, + INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON_TEST_ID, + INSIGHTS_HEADER_TEST_ID, + INSIGHTS_PREVALENCE_CONTENT_TEST_ID, + INSIGHTS_PREVALENCE_TITLE_TEST_ID, + INSIGHTS_PREVALENCE_VALUE_TEST_ID, + INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON_TEST_ID, + INVESTIGATION_GUIDE_BUTTON_TEST_ID, + INVESTIGATION_SECTION_CONTENT_TEST_ID, + INVESTIGATION_SECTION_HEADER_TEST_ID, + MITRE_ATTACK_DETAILS_TEST_ID, + MITRE_ATTACK_TITLE_TEST_ID, + REASON_DETAILS_TEST_ID, + REASON_TITLE_TEST_ID, + VISUALIZATIONS_SECTION_HEADER_TEST_ID, +} from '../../../public/flyout/right/components/test_ids'; + +/* Description section */ + +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER = + getDataTestSubjectSelector(DESCRIPTION_SECTION_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT = + getDataTestSubjectSelector(DESCRIPTION_SECTION_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE = + getDataTestSubjectSelector(DESCRIPTION_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS = getDataTestSubjectSelector( + DESCRIPTION_DETAILS_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON = + getDataTestSubjectSelector(DESCRIPTION_EXPAND_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE = + getDataTestSubjectSelector(REASON_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS = + getDataTestSubjectSelector(REASON_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE = getDataTestSubjectSelector( + MITRE_ATTACK_TITLE_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS = getDataTestSubjectSelector( + MITRE_ATTACK_DETAILS_TEST_ID +); + +/* Investigation section */ + +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER = + getDataTestSubjectSelector(INVESTIGATION_SECTION_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT = + getDataTestSubjectSelector(INVESTIGATION_SECTION_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE = + getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS = + getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK = + getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON = + getDataTestSubjectSelector(INVESTIGATION_GUIDE_BUTTON_TEST_ID); + +/* Insights section */ + +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER = + getDataTestSubjectSelector(INSIGHTS_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER = + getDataTestSubjectSelector(ENTITIES_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT = + getDataTestSubjectSelector(ENTITIES_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON = + getDataTestSubjectSelector(ENTITIES_VIEW_ALL_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER = + getDataTestSubjectSelector(ENTITY_PANEL_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT = + getDataTestSubjectSelector(ENTITY_PANEL_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER = + getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT = + getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES = + getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON = + getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER = + getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT = + getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES = + getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON = + getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON_TEST_ID); + +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER = + getDataTestSubjectSelector(INSIGHTS_PREVALENCE_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT = + getDataTestSubjectSelector(INSIGHTS_PREVALENCE_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES = + getDataTestSubjectSelector(INSIGHTS_PREVALENCE_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON = + getDataTestSubjectSelector(INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON_TEST_ID); + +/* Visualization section */ + +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER = + getDataTestSubjectSelector(VISUALIZATIONS_SECTION_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE = + getDataTestSubjectSelector(ANALYZER_TREE_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts new file mode 100644 index 0000000000000..0d23b23692f42 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getClassSelector, getDataTestSubjectSelector } from '../../helpers/common'; +import { TABLE_TAB_CONTENT_TEST_ID } from '../../../public/flyout/right/tabs/test_ids'; + +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT = + getDataTestSubjectSelector(TABLE_TAB_CONTENT_TEST_ID); + +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER = getClassSelector('euiFieldSearch'); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER = + getDataTestSubjectSelector('clearSearchButton'); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW = getDataTestSubjectSelector( + 'event-fields-table-row-@timestamp' +); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW = getDataTestSubjectSelector( + 'event-fields-table-row-_id' +); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW = getDataTestSubjectSelector( + 'event-fields-table-row-event.type' +); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN = getDataTestSubjectSelector( + 'actionItem-security-detailsFlyout-cellActions-filterIn' +); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT = getDataTestSubjectSelector( + 'actionItem-security-detailsFlyout-cellActions-filterOut' +); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS = + getDataTestSubjectSelector('showExtraActionsButton'); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE = + getDataTestSubjectSelector('actionItem-security-detailsFlyout-cellActions-addToTimeline'); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD = + getDataTestSubjectSelector('actionItem-security-detailsFlyout-cellActions-copyToClipboard'); diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/common.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/common.ts new file mode 100644 index 0000000000000..bc0119464374c --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/common.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + getDataTestSubjectSelector, + getDataTestSubjectSelectorStartWith, +} from '../../helpers/common'; + +export const KIBANA_NAVBAR_ALERTS_PAGE = getDataTestSubjectSelector( + 'solutionSideNavItemLink-alerts' +); +export const KIBANA_NAVBAR_CASES_PAGE = getDataTestSubjectSelector('solutionSideNavItemLink-cases'); +export const VIEW_CASE_TOASTER_LINK = getDataTestSubjectSelector('toaster-content-case-view-link'); +export const CREATE_CASE_BUTTON = `[data-test-subj="createNewCaseBtn"]`; +export const NEW_CASE_NAME_INPUT = `[data-test-subj="input"][aria-describedby="caseTitle"]`; +export const NEW_CASE_DESCRIPTION_INPUT = getDataTestSubjectSelector('euiMarkdownEditorTextArea'); +export const NEW_CASE_CREATE_BUTTON = getDataTestSubjectSelector('create-case-submit'); +export const EXISTING_CASE_SELECT_BUTTON = + getDataTestSubjectSelectorStartWith('cases-table-row-select-'); +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT = NEW_CASE_NAME_INPUT; +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT = + NEW_CASE_DESCRIPTION_INPUT; +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON = NEW_CASE_CREATE_BUTTON; diff --git a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts deleted file mode 100644 index ca19b115dec0d..0000000000000 --- a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* eslint-disable cypress/unsafe-to-chain-command */ - -import { - DOCUMENT_DETAILS_FLYOUT_BODY, - DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON, - DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON, - DOCUMENT_DETAILS_FLYOUT_FOOTER, - DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON, - DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, - DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB, - DOCUMENT_DETAILS_FLYOUT_JSON_TAB, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT, - DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON, - DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON, - DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER, - DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN, - KIBANA_NAVBAR_ALERTS_PAGE, - KIBANA_NAVBAR_CASES_PAGE, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON, -} from '../screens/document_expandable_flyout'; -import { EXPAND_ALERT_BTN } from '../screens/alerts'; -import { getClassSelector } from '../helpers/common'; - -/** - * Navigates to the alerts page by clicking on the Kibana sidenav entry - */ -export const navigateToAlertsPage = () => { - cy.get(KIBANA_NAVBAR_ALERTS_PAGE).click(); -}; - -/** - * Navigates to the cases page by clicking on the Kibana sidenav entry - */ -export const navigateToCasesPage = () => { - cy.get(KIBANA_NAVBAR_CASES_PAGE).click(); -}; - -/** - * Find the first alert row in the alerts table then click on the expand icon button to open the flyout - */ -export const expandFirstAlertExpandableFlyout = () => { - cy.get(EXPAND_ALERT_BTN).first().click(); -}; - -/** - * Expand the left section of the document details expandable flyout by clicking on the Find the first alert row in the alerts table then click on the expand icon button to open the flyout - */ -export const expandDocumentDetailsExpandableFlyoutLeftSection = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON).click(); - -/** - * Expand the left section of the document details expandable flyout by clicking on the Find the first alert row in the alerts table then click on the expand icon button to open the flyout - */ -export const collapseDocumentDetailsExpandableFlyoutLeftSection = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).click(); - -/** - * Scroll to x-y positions within the right section of the document details expandable flyout - * // TODO revisit this as it seems very fragile: the first element found is the timeline flyout, which isn't visible but still exist in the DOM - */ -export const scrollWithinDocumentDetailsExpandableFlyoutRightSection = (x: number, y: number) => - cy.get(getClassSelector('euiFlyout')).last().scrollTo(x, y); - -/** - * Scroll down to the flyout footer's take action button, open its dropdown and click on the desired option - */ -export const openTakeActionButtonAndSelectItem = (option: string) => { - openTakeActionButton(); - selectTakeActionItem(option); -}; - -/** - * Scroll down to the flyout footer's take action button and open its dropdown - */ -export const openTakeActionButton = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER) - .scrollIntoView() - .within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).click(); - }); -}; - -/** - * Click on the item within the flyout's footer take action button dropdown - */ -export const selectTakeActionItem = (option: string) => { - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN) - .should('be.visible') - .within(() => cy.get(option).should('be.visible').click()); -}; - -/** - * Open the Overview tab in the document details expandable flyout right section - */ -export const openOverviewTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).scrollIntoView().click(); - -/** - * Toggle the Overview tab investigation section in the document details expandable flyout right section - */ -export const toggleOverviewTabInvestigationSection = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Toggle the Overview tab description section in the document details expandable flyout right section - */ -export const toggleOverviewTabDescriptionSection = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Toggle the Overview tab insights section in the document details expandable flyout right section - */ -export const toggleOverviewTabInsightsSection = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Toggle the Overview tab visualizations section in the document details expandable flyout right section - */ -export const toggleOverviewTabVisualizationsSection = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Open the Table tab in the document details expandable flyout right section - */ -export const openTableTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).scrollIntoView().click(); - -/** - * Open the Json tab in the document details expandable flyout right section - */ -export const openJsonTab = () => cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).scrollIntoView().click(); - -/** - * Open the Visualize tab in the document details expandable flyout left section - */ -export const openVisualizeTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB).scrollIntoView().click(); - -/** - * Open the Session View under the Visualize tab in the document details expandable flyout left section - */ -export const openSessionView = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Open the Graph Analyzer under the Visuablize tab in the document details expandable flyout left section - */ -export const openGraphAnalyzer = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Open the Insights tab in the document details expandable flyout left section - */ -export const openInsightsTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView().click(); - -/** - * Open the Entities tab under the Insights tab in the document details expandable flyout left section - */ -export const openEntities = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); -/** - * Open the Threat intelligence tab under the Insights tab in the document details expandable flyout left section - */ -export const openThreatIntelligence = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Open the Prevalence tab under the Visuablize tab in the document details expandable flyout left section - */ -export const openPrevalence = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); - -/** - * Open the Correlations tab under the Visuablize tab in the document details expandable flyout left section - */ -export const openCorrelations = () => - cy - .get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) - .scrollIntoView() - .should('be.visible') - .click(); -/** - * Open the Investigations tab in the document details expandable flyout left section - */ -export const openInvestigationTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB).scrollIntoView().should('be.visible').click(); - -/** - * Open the History tab in the document details expandable flyout left section - */ -export const openHistoryTab = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB).scrollIntoView().click(); - -/** - * Filter table under the Table tab in the alert details expandable flyout right section - */ -export const filterTableTabTable = (filterValue: string) => - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER).type(filterValue); - }); - -/** - * Clear table filter under the Table tab in the alert details expandable flyout right section - */ -export const clearFilterTableTabTable = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER).click(); - }); - -/** - * Filter In action in the first table row under the Table tab in the alert details expandable flyout right section - */ -export const filterInTableTabTable = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN).first().click(); - }); - -/** - * Filter Out action in the first table row under the Table tab in the alert details expandable flyout right section - */ -export const filterOutTableTabTable = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT).first().click(); - }); - -/** - * Add to timeline action in the first table row under the Table tab in the alert details expandable flyout right section - */ -export const addToTimelineTableTabTable = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS).first().click(); - }); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE).click(); -}; - -/** - * Show Copy to clipboard button in the first table row under the Table tab in the alert details expandable flyout right section - */ -export const copyToClipboardTableTabTable = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS).first().click(); - }); -}; - -/** - * Clear filters in the alert page KQL bar - */ -export const clearFilters = () => - cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT).first().click(); - }); - -/** - * Click on the view all button under the right section, Insights, Threat Intelligence - */ -export const clickThreatIntelligenceViewAllButton = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON) - .should('be.visible') - .click(); -}; - -/** - * Click on the view all button under the right section, Insights, Prevalence - */ -export const clickPrevalenceViewAllButton = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON) - .should('be.visible') - .click(); -}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel.ts new file mode 100644 index 0000000000000..c2b1eebfefb46 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel.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 { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB } from '../../screens/expandable_flyout/alert_details_left_panel'; + +/** + * Open the Insights tab in the document details expandable flyout left section + */ +export const openInsightsTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.ts new file mode 100644 index 0000000000000..04ff01815be8e --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.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 { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON } from '../../screens/expandable_flyout/alert_details_left_panel_analyzer_graph_tab'; + +/** + * Open the Graph Analyzer under the Visuablize tab in the document details expandable flyout left section + */ +export const openGraphAnalyzerTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_correlations_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_correlations_tab.ts new file mode 100644 index 0000000000000..cf023e4bd9a68 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_correlations_tab.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 { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON } from '../../screens/expandable_flyout/alert_details_left_panel_correlations_tab'; + +/** + * Open the Correlations tab under the Visuablize tab in the document details expandable flyout left section + */ +export const openCorrelationsTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_entities_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_entities_tab.ts new file mode 100644 index 0000000000000..2c771f0c754d0 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_entities_tab.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 { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON } from '../../screens/expandable_flyout/alert_details_left_panel_entities_tab'; + +/** + * Open the Entities tab under the Insights tab in the document details expandable flyout left section + */ +export const openEntitiesTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_investigation_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_investigation_tab.ts new file mode 100644 index 0000000000000..cb52ae1203940 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_investigation_tab.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 { DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB } from '../../screens/expandable_flyout/alert_details_left_panel_investigation_tab'; + +/** + * Open the Investigations tab in the document details expandable flyout left section + */ +export const openInvestigationTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_prevalence_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_prevalence_tab.ts new file mode 100644 index 0000000000000..14f55a857960f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_prevalence_tab.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 { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON } from '../../screens/expandable_flyout/alert_details_left_panel_prevalence_tab'; + +/** + * Open the Prevalence tab under the Visualize tab in the document details expandable flyout left section + */ +export const openPrevalenceTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.ts new file mode 100644 index 0000000000000..c8744e6684783 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.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 { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON } from '../../screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab'; + +/** + * Open the Threat intelligence tab under the Insights tab in the document details expandable flyout left section + */ +export const openThreatIntelligenceTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + .should('be.visible') + .click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel.ts new file mode 100644 index 0000000000000..830154666e398 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON, + DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON, + DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER, + DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN, + DOCUMENT_DETAILS_FLYOUT_JSON_TAB, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, +} from '../../screens/expandable_flyout/alert_details_right_panel'; + +/* Header */ + +/** + * Expand the left section of the document details expandable flyout by clicking on the expand icon button + */ +export const expandDocumentDetailsExpandableFlyoutLeftSection = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON).click(); + +/** + * Expand the left section of the document details expandable flyout by clicking on the collapse icon button + */ +export const collapseDocumentDetailsExpandableFlyoutLeftSection = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).click(); + +/** + * Open the Overview tab in the document details expandable flyout right section + */ +export const openOverviewTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).click(); +}; + +/** + * Open the Table tab in the document details expandable flyout right section + */ +export const openTableTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).click(); +}; + +/** + * Open the Json tab in the document details expandable flyout right section + */ +export const openJsonTab = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).click(); +}; + +/** + * Close document details flyout + */ +export const closeFlyout = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON).click(); +}; + +/* Footer */ + +/** + * Scroll down to the flyout footer's take action button, open its dropdown and click on the desired option + */ +export const openTakeActionButtonAndSelectItem = (option: string) => { + openTakeActionButton(); + selectTakeActionItem(option); +}; + +/** + * Scroll down to the flyout footer's take action button and open its dropdown + */ +export const openTakeActionButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).within(() => + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).click() + ); +}; + +/** + * Click on the item within the flyout's footer take action button dropdown + */ +export const selectTakeActionItem = (option: string) => { + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN) + .should('be.visible') + .within(() => cy.get(option).should('be.visible').click()); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_json_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_json_tab.ts new file mode 100644 index 0000000000000..eee3c446c5a9f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_json_tab.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 { getClassSelector } from '../../helpers/common'; + +/** + * Scroll to x-y positions within the right section of the document details expandable flyout + * // TODO revisit this as it seems very fragile: the first element found is the timeline flyout, which isn't visible but still exist in the DOM + */ +export const scrollWithinDocumentDetailsExpandableFlyoutRightSection = (x: number, y: number) => + cy.get(getClassSelector('euiFlyout')).last().scrollTo(x, y); diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts new file mode 100644 index 0000000000000..ae13a474b2f64 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON, +} from '../../screens/expandable_flyout/alert_details_right_panel_overview_tab'; + +/* Description section */ + +/** + * Toggle the Overview tab description section in the document details expandable flyout right section + */ +export const toggleOverviewTabDescriptionSection = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER) + .should('be.visible') + .click(); +}; + +/* Investigation section */ + +/** + * Toggle the Overview tab investigation section in the document details expandable flyout right section + */ +export const toggleOverviewTabInvestigationSection = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) + .should('be.visible') + .click(); +}; + +/* Insights section */ + +/** + * Toggle the Overview tab insights section in the document details expandable flyout right section + */ +export const toggleOverviewTabInsightsSection = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER).should('be.visible').click(); +}; + +/** + * Click on the view all button under the right section, Insights, Entities + */ +export const clickEntitiesViewAllButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_VIEW_ALL_ENTITIES_BUTTON) + .should('be.visible') + .click(); +}; + +/** + * Click on the view all button under the right section, Insights, Threat Intelligence + */ +export const clickThreatIntelligenceViewAllButton = () => { + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VIEW_ALL_BUTTON) + .should('be.visible') + .click(); +}; + +/** + * Click on the view all button under the right section, Insights, Correlations + */ +export const clickCorrelationsViewAllButton = () => { + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VIEW_ALL_BUTTON) + .should('be.visible') + .click(); +}; + +/** + * Click on the view all button under the right section, Insights, Prevalence + */ +export const clickPrevalenceViewAllButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VIEW_ALL_BUTTON) + .should('be.visible') + .click(); +}; + +/* Visualizations section */ + +/** + * Toggle the Overview tab visualizations section in the document details expandable flyout right section + */ +export const toggleOverviewTabVisualizationsSection = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER) + .should('be.visible') + .click(); +}; + +/** + * Click on the investigation guide button under the right section, Visualization + */ +export const clickInvestigationGuideButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) + .should('be.visible') + .click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_table_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_table_tab.ts new file mode 100644 index 0000000000000..4e534ab770f26 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_table_tab.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 { DOCUMENT_DETAILS_FLYOUT_BODY } from '../../screens/expandable_flyout/alert_details_right_panel'; +import { + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT, + DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS, +} from '../../screens/expandable_flyout/alert_details_right_panel_table_tab'; + +/** + * Filter table under the Table tab in the alert details expandable flyout right section + */ +export const filterTableTabTable = (filterValue: string) => + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER).type(filterValue); + }); + +/** + * Clear table filter under the Table tab in the alert details expandable flyout right section + */ +export const clearFilterTableTabTable = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER).click(); + }); + +/** + * Filter In action in the first table row under the Table tab in the alert details expandable flyout right section + */ +export const filterInTableTabTable = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_IN).first().click(); + }); + +/** + * Filter Out action in the first table row under the Table tab in the alert details expandable flyout right section + */ +export const filterOutTableTabTable = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT).first().click(); + }); + +/** + * Add to timeline action in the first table row under the Table tab in the alert details expandable flyout right section + */ +export const addToTimelineTableTabTable = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS).first().click(); + }); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ADD_TO_TIMELINE).click(); +}; + +/** + * Show Copy to clipboard button in the first table row under the Table tab in the alert details expandable flyout right section + */ +export const copyToClipboardTableTabTable = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_MORE_ACTIONS).first().click(); + }); +}; + +/** + * Clear filters in the alert page KQL bar + */ +export const clearFilters = () => + cy.get(DOCUMENT_DETAILS_FLYOUT_BODY).within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_FILTER_OUT).first().click(); + }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/common.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/common.ts new file mode 100644 index 0000000000000..c3311d8649f09 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/common.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 { EXPAND_ALERT_BTN } from '../../screens/alerts'; +import { + CREATE_CASE_BUTTON, + KIBANA_NAVBAR_ALERTS_PAGE, + KIBANA_NAVBAR_CASES_PAGE, + NEW_CASE_CREATE_BUTTON, + NEW_CASE_DESCRIPTION_INPUT, + NEW_CASE_NAME_INPUT, +} from '../../screens/expandable_flyout/common'; + +/** + * Navigates to the alerts page by clicking on the Kibana sidenav entry + */ +export const navigateToAlertsPage = () => { + cy.get(KIBANA_NAVBAR_ALERTS_PAGE).should('be.visible').click(); +}; + +/** + * Navigates to the cases page by clicking on the Kibana sidenav entry + */ +export const navigateToCasesPage = () => { + cy.get(KIBANA_NAVBAR_CASES_PAGE).click(); +}; + +/** + * Find the first alert row in the alerts table then click on the expand icon button to open the flyout + */ +export const expandFirstAlertExpandableFlyout = () => { + cy.get(EXPAND_ALERT_BTN).first().click(); +}; + +/** + * Create a new case from the cases page + */ +export const createNewCaseFromCases = () => { + cy.get(CREATE_CASE_BUTTON).should('be.visible').click(); + cy.get(NEW_CASE_NAME_INPUT).should('be.visible').click(); + cy.get(NEW_CASE_NAME_INPUT).type('case'); + cy.get(NEW_CASE_DESCRIPTION_INPUT).should('be.visible').click(); + cy.get(NEW_CASE_DESCRIPTION_INPUT).type('case description'); + cy.get(NEW_CASE_CREATE_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts index d79df35bcfe2c..d4db9ae7b9183 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts @@ -6,22 +6,18 @@ */ import type { RenderHookResult, RenderResult } from '@testing-library/react-hooks'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import type { EndpointPrivileges } from '../../../../../common/endpoint/types'; -import { useCurrentUser, useKibana, useHttp as _useHttp } from '../../../lib/kibana'; +import { useCurrentUser, useKibana } from '../../../lib/kibana'; import { licenseService } from '../../../hooks/use_license'; import { useEndpointPrivileges } from './use_endpoint_privileges'; import { getEndpointPrivilegesInitialStateMock } from './mocks'; import { getEndpointPrivilegesInitialState } from './utils'; -import { exceptionsListAllHttpMocks } from '../../../../management/mocks'; -import { getDeferred } from '../../../../management/mocks/utils'; -import { waitFor } from '@testing-library/react'; -import type { HttpFetchOptionsWithPath, HttpSetup } from '@kbn/core-http-browser'; jest.mock('../../../lib/kibana'); jest.mock('../../../hooks/use_license', () => { @@ -38,7 +34,6 @@ jest.mock('../../../hooks/use_license', () => { }); const useKibanaMock = useKibana as jest.Mocked; -const useHttpMock = _useHttp as jest.Mock; const licenseServiceMock = licenseService as jest.Mocked; describe('When using useEndpointPrivileges hook', () => { @@ -98,61 +93,4 @@ describe('When using useEndpointPrivileges hook', () => { render(); expect(result.current).toEqual({ ...getEndpointPrivilegesInitialState(), loading: false }); }); - - it.each([ - ['HIE exist', true], - ['No HIE exist', false], - ])( - `should check if Host Isolation Exceptions exist when license is not Platinum+ (%s)`, - async (_, hasHIE) => { - licenseServiceMock.isPlatinumPlus.mockReturnValue(false); - const http = useKibanaMock().services.http as jest.Mocked; - const deferred = getDeferred(); - const apiMock = exceptionsListAllHttpMocks(http); - - useHttpMock.mockReturnValue(http); - - const apiResponse = apiMock.responseProvider.exceptionsFind({ - query: {}, - } as HttpFetchOptionsWithPath); - apiMock.responseProvider.exceptionsFind.mockImplementation(() => { - if (hasHIE) { - return apiResponse; - } - return { - ...apiResponse, - total: 0, - data: [], - }; - }); - - // Hold on to the Host Isolation Exceptions API all - apiMock.responseProvider.exceptionsFind.mockDelay.mockReturnValue(deferred.promise); - - const { rerender } = render(); - - expect(result.current).toEqual(getEndpointPrivilegesInitialState()); - - // release HIE api call - act(() => { - deferred.resolve(); - }); - rerender(); - - await waitFor(() => { - expect(apiMock.responseProvider.exceptionsFind).toHaveBeenCalled(); - }); - - expect(result.current).toEqual( - getEndpointPrivilegesInitialStateMock({ - canCreateArtifactsByPolicy: false, - canIsolateHost: false, - canAccessEndpointActionsLogManagement: false, - canWriteHostIsolationExceptions: false, - canReadHostIsolationExceptions: hasHIE, - canDeleteHostIsolationExceptions: hasHIE, - }) - ); - } - ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts index 5fa2400dab6b9..c98e462eb540b 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts @@ -8,9 +8,7 @@ import { useEffect, useMemo, useState } from 'react'; import { isEmpty } from 'lodash'; import { useIsMounted } from '@kbn/securitysolution-hook-utils'; -import { checkArtifactHasData } from '../../../../management/services/exceptions_list/check_artifact_has_data'; -import { HostIsolationExceptionsApiClient } from '../../../../management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client'; -import { useCurrentUser, useHttp, useKibana } from '../../../lib/kibana'; +import { useCurrentUser, useKibana } from '../../../lib/kibana'; import { useLicense } from '../../../hooks/use_license'; import type { EndpointPrivileges, @@ -31,7 +29,6 @@ import { useSecuritySolutionStartDependencies } from './security_solution_start_ */ export const useEndpointPrivileges = (): Immutable => { const isMounted = useIsMounted(); - const http = useHttp(); const user = useCurrentUser(); const kibanaServices = useKibana().services; @@ -43,42 +40,21 @@ export const useEndpointPrivileges = (): Immutable => { const fleetAuthz = fleetServicesFromUseKibana?.authz ?? fleetServicesFromPluginStart?.authz; const licenseService = useLicense(); - const isPlatinumPlus = licenseService.isPlatinumPlus(); const [userRolesCheckDone, setUserRolesCheckDone] = useState(false); const [userRoles, setUserRoles] = useState>([]); - const [checkHostIsolationExceptionsDone, setCheckHostIsolationExceptionsDone] = - useState(false); - const [hasHostIsolationExceptionsItems, setHasHostIsolationExceptionsItems] = - useState(false); - const privileges = useMemo(() => { - const loading = !userRolesCheckDone || !user || !checkHostIsolationExceptionsDone; + const loading = !userRolesCheckDone || !user; const privilegeList: EndpointPrivileges = Object.freeze({ loading, ...(!loading && fleetAuthz && !isEmpty(user) - ? calculateEndpointAuthz( - licenseService, - fleetAuthz, - userRoles, - true, - hasHostIsolationExceptionsItems - ) + ? calculateEndpointAuthz(licenseService, fleetAuthz, userRoles) : getEndpointAuthzInitialState()), }); - return privilegeList; - }, [ - userRolesCheckDone, - user, - checkHostIsolationExceptionsDone, - fleetAuthz, - licenseService, - userRoles, - hasHostIsolationExceptionsItems, - ]); + }, [userRolesCheckDone, user, fleetAuthz, licenseService, userRoles]); // get user roles useEffect(() => { @@ -90,29 +66,5 @@ export const useEndpointPrivileges = (): Immutable => { })(); }, [isMounted, user]); - // Check if Host Isolation Exceptions exist if license is not Platinum+ - useEffect(() => { - if (!isPlatinumPlus) { - // Reset these back to false. Case license is changed while the user is logged in. - setHasHostIsolationExceptionsItems(false); - setCheckHostIsolationExceptionsDone(false); - - checkArtifactHasData(HostIsolationExceptionsApiClient.getInstance(http)) - .then((hasData) => { - if (isMounted()) { - setHasHostIsolationExceptionsItems(hasData); - } - }) - .finally(() => { - if (isMounted()) { - setCheckHostIsolationExceptionsDone(true); - } - }); - } else { - setHasHostIsolationExceptionsItems(true); - setCheckHostIsolationExceptionsDone(true); - } - }, [http, isMounted, isPlatinumPlus]); - return privileges; }; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts index 82411e978048d..830483cd5ef01 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts @@ -26,7 +26,6 @@ export const HOST_DETAILS_INFO_TEST_ID = 'host-overview'; export const HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID = `${PREFIX}HostsDetailsRelatedUsersTable` as const; -export const THREAT_INTELLIGENCE_DETAILS_TEST_ID = `${PREFIX}ThreatIntelligenceDetails` as const; export const PREVALENCE_DETAILS_TEST_ID = `${PREFIX}PrevalenceDetails` as const; export const CORRELATIONS_DETAILS_TEST_ID = `${PREFIX}CorrelationsDetails` as const; @@ -34,8 +33,6 @@ export const THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID = `threat-match-det export const THREAT_INTELLIGENCE_DETAILS_SPINNER_TEST_ID = `${PREFIX}ThreatIntelligenceDetailsLoadingSpinner` as const; -export const INVESTIGATION_TEST_ID = `${PREFIX}Investigation` as const; - export const RESPONSE_BASE_TEST_ID = `${PREFIX}Responses` as const; export const RESPONSE_DETAILS_TEST_ID = `${RESPONSE_BASE_TEST_ID}Details` as const; export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_BASE_TEST_ID}Empty` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx index 6c08fa5b1d2c1..0dadc305e6867 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiPanel } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { InsightsSummaryRow } from './insights_summary_row'; @@ -44,6 +44,20 @@ export const CorrelationsOverview: React.FC = () => { scopeId, }); + const correlationRows = useMemo( + () => + data.map((d) => ( + + )), + [data] + ); + return ( { > - {data.map((d) => ( - - ))} + {correlationRows} { - const originalDocumentId = find( - { category: 'kibana', field: 'kibana.alert.ancestors.id' }, - dataFormattedForFieldBrowser - ); - const originalDocumentIndex = find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, - dataFormattedForFieldBrowser + const documentId = useMemo(() => { + const originalDocumentId = find( + { category: 'kibana', field: 'kibana.alert.ancestors.id' }, + dataFormattedForFieldBrowser + ); + const { values } = originalDocumentId ?? { values: [] }; + return Array.isArray(values) ? values[0] : ''; + }, [dataFormattedForFieldBrowser]); + + const { values: indices } = useMemo( + () => + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, + dataFormattedForFieldBrowser + ) || { values: [] }, + [dataFormattedForFieldBrowser] ); + const isActiveTimelines = isActiveTimeline(scopeId ?? ''); - const { values: indices } = originalDocumentIndex || { values: [] }; - const { values: wrappedDocumentId } = originalDocumentId || { values: [] }; - const documentId = Array.isArray(wrappedDocumentId) ? wrappedDocumentId[0] : ''; const { loading, error, alertIds } = useAlertPrevalenceFromProcessTree({ isActiveTimeline: isActiveTimelines, documentId, - indices: indices ?? [], + indices: indices || [], }); return { diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_related_alerts_by_same_source_event.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_related_alerts_by_same_source_event.ts index 7232a0406843a..1d9052f8f3233 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_related_alerts_by_same_source_event.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_related_alerts_by_same_source_event.ts @@ -7,6 +7,7 @@ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { find } from 'lodash/fp'; +import { useMemo } from 'react'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; import { isActiveTimeline } from '../../../helpers'; @@ -46,11 +47,15 @@ export const useFetchRelatedAlertsBySameSourceEvent = ({ dataFormattedForFieldBrowser, scopeId, }: UseFetchRelatedAlertsBySameSourceEventParams): UseFetchRelatedAlertsBySameSourceEventResult => { - const sourceEventField = find( - { category: 'kibana', field: 'kibana.alert.original_event.id' }, - dataFormattedForFieldBrowser + const { field, values } = useMemo( + () => + find( + { category: 'kibana', field: 'kibana.alert.original_event.id' }, + dataFormattedForFieldBrowser + ) || { field: '', values: [] }, + [dataFormattedForFieldBrowser] ); - const { field, values } = sourceEventField || { field: '', values: [] }; + const { loading, error, count, alertIds } = useAlertPrevalence({ field, value: values, diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx index 1e8017b565d29..554e8206f8b93 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx @@ -53,7 +53,7 @@ export const usePrevalence = ({ dataFormattedForFieldBrowser, scopeId, }: UsePrevalenceParams): UsePrevalenceResult => { - const [count, setCount] = useState(0); + const [count, setCount] = useState(0); // TODO this needs to be changed at it causes a re-render when the count is updated // retrieves the highlighted fields const summaryRows = useMemo( @@ -77,6 +77,7 @@ export const usePrevalence = ({ scopeId={scopeId} callbackIfNull={() => setCount((prevCount) => prevCount + 1)} data-test-subj={INSIGHTS_PREVALENCE_TEST_ID} + key={row.description.data.field} /> )), [summaryRows, scopeId] diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx index ec75d6aaa28df..7e7c4cec2c06b 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx @@ -50,6 +50,7 @@ describe('When using the release action from response actions console', () => { endpointCapabilities: [...capabilities], endpointPrivileges: { ...getEndpointAuthzInitialState(), + canUnIsolateHost: true, loading: false, }, }), diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts index 664bd562ec235..17116530e6467 100644 --- a/x-pack/plugins/security_solution/public/management/links.test.ts +++ b/x-pack/plugins/security_solution/public/management/links.test.ts @@ -19,10 +19,7 @@ import { getEndpointAuthzInitialStateMock } from '../../common/endpoint/service/ import { licenseService as _licenseService } from '../common/hooks/use_license'; import type { LicenseService } from '../../common/license'; import { createLicenseServiceMock } from '../../common/license/mocks'; -import type { FleetAuthz } from '@kbn/fleet-plugin/common'; import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; -import type { DeepPartial } from '@kbn/utility-types'; -import { merge } from 'lodash'; import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; jest.mock('../common/hooks/use_license'); @@ -48,21 +45,17 @@ describe('links', () => { links: links.links?.filter((link) => !excludedLinks.includes(link.id)), }); - const getPlugins = ( - roles: string[], - fleetAuthzOverrides: DeepPartial = {}, - noUserAuthz: boolean = false - ): StartPlugins => { + const getPlugins = (noUserAuthz: boolean = false): StartPlugins => { return { security: { authc: { getCurrentUser: noUserAuthz - ? jest.fn().mockReturnValue('') - : jest.fn().mockReturnValue({ roles }), + ? jest.fn().mockReturnValue(undefined) + : jest.fn().mockReturnValue([]), }, }, fleet: { - authz: merge(createFleetAuthzMock(), fleetAuthzOverrides), + authz: createFleetAuthzMock(), }, } as unknown as StartPlugins; }; @@ -86,15 +79,12 @@ describe('links', () => { it('should return all links for user with all sub-feature privileges', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue(getEndpointAuthzInitialStateMock()); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(links); }); it('should not return any endpoint management link for user with all sub-feature privileges when no user authz', async () => { - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins([], {}, true) - ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins(true)); expect(filteredLinks).toEqual( getLinksWithout( SecurityPageName.blocklist, @@ -113,106 +103,80 @@ describe('links', () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue( getEndpointAuthzInitialStateMock({ canReadActionsLogManagement: false, + canDeleteHostIsolationExceptions: false, }) ); fakeHttpServices.get.mockResolvedValue({ total: 0 }); - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins(['superuser']) - ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.responseActionsHistory)); }); }); describe('Host Isolation Exception', () => { - it('should NOT return HIE if `canReadHostIsolationExceptions` is false', async () => { + it('should return HIE if user has access permission (licensed)', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue( - getEndpointAuthzInitialStateMock({ canReadHostIsolationExceptions: false }) + getEndpointAuthzInitialStateMock({ canAccessHostIsolationExceptions: true }) ); - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins(['superuser']) + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); + + expect(filteredLinks).toEqual(links); + expect(fakeHttpServices.get).not.toHaveBeenCalled(); + }); + + it('should NOT return HIE if the user has no HIE permission', async () => { + (calculateEndpointAuthz as jest.Mock).mockReturnValue( + getEndpointAuthzInitialStateMock({ + canAccessHostIsolationExceptions: false, + canReadHostIsolationExceptions: false, + }) ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); + expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions)); + expect(fakeHttpServices.get).not.toHaveBeenCalled(); }); - it('should NOT return HIE if license is lower than Enterprise and NO HIE entries exist', async () => { + it('should NOT return HIE if user has read permission (no license) and NO HIE entries exist', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue( - getEndpointAuthzInitialStateMock({ canReadHostIsolationExceptions: false }) + getEndpointAuthzInitialStateMock({ + canAccessHostIsolationExceptions: false, + canReadHostIsolationExceptions: true, + }) ); fakeHttpServices.get.mockResolvedValue({ total: 0 }); - licenseServiceMock.isPlatinumPlus.mockReturnValue(false); - - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins([], { - packagePrivileges: { - endpoint: { - actions: { - readHostIsolationExceptions: { - executePackageAction: true, - }, - }, - }, - }, - }) - ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); + + expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions)); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', { query: expect.objectContaining({ list_id: [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id], }), }); - expect(calculateEndpointAuthz as jest.Mock).toHaveBeenLastCalledWith( - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - false - ); - expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions)); }); - it('should return HIE if license is lower than Enterprise, but HIE entries exist', async () => { + it('should return HIE if user has read permission (no license) but HIE entries exist', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue( - getEndpointAuthzInitialStateMock({ canReadHostIsolationExceptions: true }) + getEndpointAuthzInitialStateMock({ + canAccessHostIsolationExceptions: false, + canReadHostIsolationExceptions: true, + }) ); fakeHttpServices.get.mockResolvedValue({ total: 100 }); - licenseServiceMock.isPlatinumPlus.mockReturnValue(false); - - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins([], { - packagePrivileges: { - endpoint: { - actions: { - readHostIsolationExceptions: { - executePackageAction: true, - }, - }, - }, - }, - }) - ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); + + expect(filteredLinks).toEqual(links); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', { query: expect.objectContaining({ list_id: [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id], }), }); - expect(calculateEndpointAuthz as jest.Mock).toHaveBeenLastCalledWith( - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - true - ); - expect(filteredLinks).toEqual(getLinksWithout()); }); }); @@ -220,7 +184,7 @@ describe('links', () => { it('should return all links for user with all sub-feature privileges', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue(getEndpointAuthzInitialStateMock()); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(links); }); @@ -232,7 +196,7 @@ describe('links', () => { }) ); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.trustedApps)); }); @@ -244,7 +208,7 @@ describe('links', () => { }) ); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.eventFilters)); }); @@ -256,7 +220,7 @@ describe('links', () => { }) ); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.blocklist)); }); @@ -268,7 +232,7 @@ describe('links', () => { }) ); - const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.policies)); }); @@ -281,10 +245,7 @@ describe('links', () => { canReadEndpointList: false, }) ); - const filteredLinks = await getManagementFilteredLinks( - coreMockStarted, - getPlugins(['superuser']) - ); + const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.endpoints)); }); }); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index daf0df2d4736f..745f639438352 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -8,7 +8,6 @@ import type { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { hasKibanaPrivilege } from '../../common/endpoint/service/authz/authz'; import { checkArtifactHasData } from './services/exceptions_list/check_artifact_has_data'; import { calculateEndpointAuthz, @@ -239,35 +238,10 @@ export const getManagementFilteredLinks = async ( plugins: StartPlugins ): Promise => { const fleetAuthz = plugins.fleet?.authz; - const linksToExclude: SecurityPageName[] = []; const currentUser = await plugins.security.authc.getCurrentUser(); - const isPlatinumPlus = licenseService.isPlatinumPlus(); - let hasHostIsolationExceptions: boolean = isPlatinumPlus; - - // If not Platinum+ license and user has read permissions to security solution - // then check if Host Isolation Exceptions exist. - // *** IT IS IMPORTANT *** that this HTTP call only be made if the user has access to the - // Lists plugin, else non-security solution users, especially when license is not Platinum, - // may see failed HTTP requests in the browser console. This is the reason that - // `hasKibanaPrivilege()` is used below. - if ( - currentUser && - !isPlatinumPlus && - fleetAuthz && - hasKibanaPrivilege( - fleetAuthz, - true, - currentUser.roles.includes('superuser'), - 'readHostIsolationExceptions' - ) - ) { - hasHostIsolationExceptions = await checkArtifactHasData( - HostIsolationExceptionsApiClient.getInstance(core.http) - ); - } - const { canReadActionsLogManagement, + canAccessHostIsolationExceptions, canReadHostIsolationExceptions, canReadEndpointList, canReadTrustedApplications, @@ -276,15 +250,18 @@ export const getManagementFilteredLinks = async ( canReadPolicyManagement, } = fleetAuthz && currentUser - ? calculateEndpointAuthz( - licenseService, - fleetAuthz, - currentUser.roles, - true, - hasHostIsolationExceptions - ) + ? calculateEndpointAuthz(licenseService, fleetAuthz, currentUser.roles) : getEndpointAuthzInitialState(); + const showHostIsolationExceptions = + canAccessHostIsolationExceptions || // access host isolation exceptions is a paid feature, always show the link. + // read host isolation exceptions is not a paid feature, to allow deleting exceptions after a downgrade scenario. + // however, in this situation we allow to access only when there is data, otherwise the link won't be accessible. + (canReadHostIsolationExceptions && + (await checkArtifactHasData(HostIsolationExceptionsApiClient.getInstance(core.http)))); + + const linksToExclude: SecurityPageName[] = []; + if (!canReadEndpointList) { linksToExclude.push(SecurityPageName.endpoints); } @@ -297,7 +274,7 @@ export const getManagementFilteredLinks = async ( linksToExclude.push(SecurityPageName.responseActionsHistory); } - if (!canReadHostIsolationExceptions) { + if (!showHostIsolationExceptions) { linksToExclude.push(SecurityPageName.hostIsolationExceptions); } diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx index 5f2d528ecbfcd..1d6c12c1ed27e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx @@ -5,26 +5,28 @@ * 2.0. */ -import { Switch, Redirect } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import React, { memo } from 'react'; -import { ENDPOINTS_PATH, SecurityPageName } from '../../../../common/constants'; +import { SecurityPageName } from '../../../../common/constants'; import { useLinkExists } from '../../../common/links/links'; import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; import { HostIsolationExceptionsList } from './view/host_isolation_exceptions_list'; +import { NoPrivilegesPage } from '../../../common/components/no_privileges'; /** * Provides the routing container for the hosts related views */ export const HostIsolationExceptionsContainer = memo(() => { - // TODO: Probably should not silently redirect here const canAccessHostIsolationExceptionsLink = useLinkExists( SecurityPageName.hostIsolationExceptions ); - if (!canAccessHostIsolationExceptionsLink) { - return ; + // TODO: Render a license/productType upsell page + return ( + securitySolution.privileges} /> + ); } return ( diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts index 865314134e5c0..f15d139dd78d5 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts @@ -41,19 +41,22 @@ import pRetry from 'p-retry'; import { renderSummaryTable } from './print_run'; import { getLocalhostRealIp } from '../endpoint/common/localhost_services'; -const retrieveIntegrations = ( - specPattern: string[], - chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) - : 1, - chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) - : 0 -) => { +const retrieveIntegrations = (specPattern: string[]) => { const integrationsPaths = globby.sync(specPattern); - const chunkSize = Math.ceil(integrationsPaths.length / chunksTotal); - return _.chunk(integrationsPaths, chunkSize)[chunkIndex]; + if (process.env.RUN_ALL_TESTS === 'true') { + return integrationsPaths; + } else { + const chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) + : 1; + const chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) + : 0; + const chunkSize = Math.ceil(integrationsPaths.length / chunksTotal); + + return _.chunk(integrationsPaths, chunkSize)[chunkIndex]; + } }; export const cli = () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 8db3c3cd7df64..278325a972011 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -15,7 +15,6 @@ import type { FleetFromHostFileClientInterface, } from '@kbn/fleet-plugin/server'; import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; -import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { getPackagePolicyCreateCallback, @@ -42,7 +41,6 @@ import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; import type { FeatureUsageService } from './services/feature_usage/service'; import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { ActionCreateService } from './services'; -import { doesArtifactHaveData } from './services'; import type { actionCreateService } from './services/actions'; export interface EndpointAppContextServiceSetupContract { @@ -162,20 +160,7 @@ export class EndpointAppContextService { public async getEndpointAuthz(request: KibanaRequest): Promise { const fleetAuthz = await this.getFleetAuthzService().fromRequest(request); const userRoles = this.security?.authc.getCurrentUser(request)?.roles ?? []; - const isPlatinumPlus = this.getLicenseService().isPlatinumPlus(); - const listClient = this.getExceptionListsClient(); - - const hasExceptionsListItems = !isPlatinumPlus - ? await doesArtifactHaveData(listClient, ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID) - : true; - - return calculateEndpointAuthz( - this.getLicenseService(), - fleetAuthz, - userRoles, - true, - hasExceptionsListItems - ); + return calculateEndpointAuthz(this.getLicenseService(), fleetAuthz, userRoles); } public getEndpointMetadataService(): EndpointMetadataService { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 1d82c9faa5461..f176134ffe3a0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -218,10 +218,11 @@ function redirectHandler( TypeOf, SecuritySolutionRequestHandlerContext > { - return async (_context, _req, res) => { + return async (context, _req, res) => { + const basePath = (await context.securitySolution).getServerBasePath(); return res.custom({ statusCode: 308, - headers: { location }, + headers: { location: `${basePath}${location}` }, }); }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index dda2d60ca94e3..aaf16057a0df9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -5,7 +5,6 @@ * 2.0. */ -import LRU from 'lru-cache'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import type { Logger } from '@kbn/core/server'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; @@ -62,7 +61,6 @@ export enum ManifestManagerMockType { } export interface ManifestManagerMockOptions { - cache: LRU; exceptionListClient: ExceptionListClient; packagePolicyService: jest.Mocked; savedObjectsClient: ReturnType; @@ -73,7 +71,6 @@ export const buildManifestManagerMockOptions = ( ): ManifestManagerMockOptions => { const savedObjectMock = savedObjectsClientMock.create(); return { - cache: new LRU({ max: 10, maxAge: 1000 * 60 * 60 }), exceptionListClient: listMock.getExceptionListClient(savedObjectMock), packagePolicyService: createPackagePolicyServiceMock(), savedObjectsClient: savedObjectMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 86ca4787969f8..28933fb35e81c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -616,7 +616,7 @@ describe('ManifestManager', () => { }); describe('pushArtifacts', () => { - test('Successfully invokes artifactClient and stores in the cache', async () => { + test('Successfully invokes artifactClient', async () => { const context = buildManifestManagerContextMock({}); const artifactClient = context.artifactClient as jest.Mocked; const manifestManager = new ManifestManager(context); @@ -637,12 +637,6 @@ describe('ManifestManager', () => { ...ARTIFACT_EXCEPTIONS_WINDOWS, }, ]); - expect( - JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!.toString()) - ).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_MACOS)); - expect( - JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))!.toString()) - ).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_WINDOWS)); }); test('Returns errors for partial failures', async () => { @@ -684,10 +678,6 @@ describe('ManifestManager', () => { ...ARTIFACT_EXCEPTIONS_WINDOWS, }, ]); - expect( - JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!.toString()) - ).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_MACOS)); - expect(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))).toBeUndefined(); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 8b213f26eab1c..440e6366e262f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -7,7 +7,6 @@ import pMap from 'p-map'; import semver from 'semver'; -import type LRU from 'lru-cache'; import { isEqual, isEmpty } from 'lodash'; import { type Logger, type SavedObjectsClientContract } from '@kbn/core/server'; import { @@ -90,7 +89,6 @@ export interface ManifestManagerContext { exceptionListClient: ExceptionListClient; packagePolicyService: PackagePolicyClient; logger: Logger; - cache: LRU; experimentalFeatures: ExperimentalFeatures; } @@ -108,7 +106,6 @@ export class ManifestManager { protected packagePolicyService: PackagePolicyClient; protected savedObjectsClient: SavedObjectsClientContract; protected logger: Logger; - protected cache: LRU; protected schemaVersion: ManifestSchemaVersion; protected experimentalFeatures: ExperimentalFeatures; @@ -118,7 +115,6 @@ export class ManifestManager { this.packagePolicyService = context.packagePolicyService; this.savedObjectsClient = context.savedObjectsClient; this.logger = context.logger; - this.cache = context.cache; this.schemaVersion = 'v1'; this.experimentalFeatures = context.experimentalFeatures; } @@ -374,8 +370,7 @@ export class ManifestManager { const fleetArtifact = fleetArtfactsByIdentifier[artifactId]; if (!fleetArtifact) return; - // Cache the compressed body of the artifact - this.cache.set(artifactId, Buffer.from(artifact.body, 'base64')); + newManifest.replaceArtifact(fleetArtifact); }); } @@ -386,8 +381,6 @@ export class ManifestManager { /** * Deletes outdated artifact SOs. * - * The artifact may still remain in the cache. - * * @param artifactIds The IDs of the artifact to delete.. * @returns {Promise} Any errors encountered. */ diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts index 4a6f799f5cc91..fb56c33f4e39b 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts @@ -71,7 +71,7 @@ export class AppFeatures { this.experimentalFeatures ); const enabledSecurityAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs( - getSecurityAppFeaturesConfig() + getSecurityAppFeaturesConfig(this.experimentalFeatures) ); this.featuresSetup.registerKibanaFeature( this.securityFeatureConfigMerger.mergeAppFeatureConfigs( diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts index 4bf4ceed34aad..f8519a03ef043 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts @@ -124,23 +124,10 @@ export const getSecurityBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({ export const getSecurityBaseKibanaSubFeatureIds = ( _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use -): SecuritySubFeatureId[] => { - const subFeatureIds: SecuritySubFeatureId[] = [ - SecuritySubFeatureId.endpointList, - SecuritySubFeatureId.trustedApplications, - SecuritySubFeatureId.hostIsolationExceptions, - SecuritySubFeatureId.blocklist, - SecuritySubFeatureId.eventFilters, - SecuritySubFeatureId.policyManagement, - SecuritySubFeatureId.responseActionsHistory, - SecuritySubFeatureId.hostIsolation, - SecuritySubFeatureId.processOperations, - SecuritySubFeatureId.fileOperations, - SecuritySubFeatureId.executeAction, - ]; - - return subFeatureIds; -}; +): SecuritySubFeatureId[] => [ + SecuritySubFeatureId.hostIsolationExceptions, + SecuritySubFeatureId.hostIsolation, +]; /** * Maps the AppFeatures keys to Kibana privileges that will be merged @@ -151,7 +138,9 @@ export const getSecurityBaseKibanaSubFeatureIds = ( * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. */ -export const getSecurityAppFeaturesConfig = (): AppFeaturesSecurityConfig => { +export const getSecurityAppFeaturesConfig = ( + _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use +): AppFeaturesSecurityConfig => { return { [AppFeatureSecurityKey.advancedInsights]: { privileges: { @@ -165,5 +154,46 @@ export const getSecurityAppFeaturesConfig = (): AppFeaturesSecurityConfig => { }, }, }, + + [AppFeatureSecurityKey.endpointResponseActions]: { + subFeatureIds: [ + SecuritySubFeatureId.processOperations, + SecuritySubFeatureId.fileOperations, + SecuritySubFeatureId.executeAction, + ], + subFeaturesPrivileges: [ + { + id: 'host_isolation_all', + api: [`${APP_ID}-writeHostIsolation`], + ui: ['writeHostIsolation'], + }, + ], + }, + + [AppFeatureSecurityKey.endpointExceptions]: { + subFeatureIds: [ + SecuritySubFeatureId.trustedApplications, + SecuritySubFeatureId.blocklist, + SecuritySubFeatureId.eventFilters, + SecuritySubFeatureId.policyManagement, + SecuritySubFeatureId.endpointList, + SecuritySubFeatureId.responseActionsHistory, + ], + subFeaturesPrivileges: [ + { + id: 'host_isolation_exceptions_all', + api: [ + `${APP_ID}-accessHostIsolationExceptions`, + `${APP_ID}-writeHostIsolationExceptions`, + ], + ui: ['accessHostIsolationExceptions', 'writeHostIsolationExceptions'], + }, + { + id: 'host_isolation_exceptions_read', + api: [`${APP_ID}-accessHostIsolationExceptions`], + ui: ['accessHostIsolationExceptions'], + }, + ], + }, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts index 2f3204be5a2f4..a06ac83054560 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts @@ -142,7 +142,7 @@ const hostIsolationExceptionsSubFeature: SubFeatureConfig = { 'lists-all', 'lists-read', 'lists-summary', - `${APP_ID}-writeHostIsolationExceptions`, + `${APP_ID}-deleteHostIsolationExceptions`, `${APP_ID}-readHostIsolationExceptions`, ], id: 'host_isolation_exceptions_all', @@ -152,7 +152,7 @@ const hostIsolationExceptionsSubFeature: SubFeatureConfig = { all: [EXCEPTION_LIST_NAMESPACE_AGNOSTIC], read: [], }, - ui: ['writeHostIsolationExceptions', 'readHostIsolationExceptions'], + ui: ['readHostIsolationExceptions', 'deleteHostIsolationExceptions'], }, { api: ['lists-read', 'lists-summary', `${APP_ID}-readHostIsolationExceptions`], @@ -396,7 +396,7 @@ const hostIsolationSubFeature: SubFeatureConfig = { groupType: 'mutually_exclusive', privileges: [ { - api: [`${APP_ID}-writeHostIsolation`], + api: [`${APP_ID}-writeHostIsolationRelease`], id: 'host_isolation_all', includeIn: 'none', name: 'All', @@ -404,7 +404,7 @@ const hostIsolationSubFeature: SubFeatureConfig = { all: [], read: [], }, - ui: ['writeHostIsolation'], + ui: ['writeHostIsolationRelease'], }, ], }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index bfda85063f2b9..8bc4dfce70cfa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -109,6 +109,7 @@ const createSecuritySolutionRequestContextMock = ( return { core, + getServerBasePath: jest.fn(() => ''), getEndpointAuthz: jest.fn(async () => getEndpointAuthzInitialStateMock(overrides.endpointAuthz) ), diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 58aa81aefeefc..52bcc394fcc80 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -6,7 +6,6 @@ */ import type { Observable } from 'rxjs'; -import LRU from 'lru-cache'; import { QUERY_RULE_TYPE_ID, SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import type { Logger } from '@kbn/core/server'; import { SavedObjectsClient } from '@kbn/core/server'; @@ -111,7 +110,6 @@ export class Plugin implements ISecuritySolutionPlugin { private manifestTask: ManifestTask | undefined; private checkMetadataTransformsTask: CheckMetadataTransformsTask | undefined; - private artifactsCache: LRU; private telemetryUsageCounter?: UsageCounter; private endpointContext: EndpointAppContext; @@ -124,8 +122,6 @@ export class Plugin implements ISecuritySolutionPlugin { this.appClientFactory = new AppClientFactory(); this.appFeatures = new AppFeatures(this.logger, this.config.experimentalFeatures); - // Cache up to three artifacts with a max retention of 5 mins each - this.artifactsCache = new LRU({ max: 3, maxAge: 1000 * 60 * 5 }); this.telemetryEventsSender = new TelemetryEventsSender(this.logger); this.telemetryReceiver = new TelemetryReceiver(this.logger); @@ -430,7 +426,6 @@ export class Plugin implements ISecuritySolutionPlugin { exceptionListClient, packagePolicyService: plugins.fleet.packagePolicyService, logger, - cache: this.artifactsCache, experimentalFeatures: config.experimentalFeatures, }); diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index 3bb125cf06914..e1b8e1aa8fb48 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -77,6 +77,8 @@ export class RequestContextFactory implements IRequestContextFactory { return { core: coreContext, + getServerBasePath: () => core.http.basePath.serverBasePath, + getEndpointAuthz: async (): Promise> => { if (!endpointAuthz) { // eslint-disable-next-line require-atomic-updates diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts index a29193250ea28..6bd5b13d08d5b 100644 --- a/x-pack/plugins/security_solution/server/types.ts +++ b/x-pack/plugins/security_solution/server/types.ts @@ -31,6 +31,7 @@ export { AppClient }; export interface SecuritySolutionApiRequestHandlerContext { core: CoreRequestHandlerContext; + getServerBasePath: () => string; getEndpointAuthz: () => Promise>; getConfig: () => ConfigType; getFrameworkRequest: () => FrameworkRequest; diff --git a/x-pack/plugins/serverless_security/common/pli/pli_config.ts b/x-pack/plugins/serverless_security/common/pli/pli_config.ts index 01ef77d6e0635..f848cc7c7e112 100644 --- a/x-pack/plugins/serverless_security/common/pli/pli_config.ts +++ b/x-pack/plugins/serverless_security/common/pli/pli_config.ts @@ -18,8 +18,8 @@ export const PLI_APP_FEATURES: PliAppFeatures = { complete: [AppFeatureKey.advancedInsights, AppFeatureKey.casesConnectors], }, endpoint: { - essentials: [], - complete: [], + essentials: [AppFeatureKey.endpointExceptions], + complete: [AppFeatureKey.endpointResponseActions], }, cloud: { essentials: [], diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 706e9dbe0e1a6..6b6f049f79bc1 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12980,7 +12980,6 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "Nommer votre application de recherche", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "Sélectionner les index", "xpack.enterpriseSearch.content.engines.createEngine.submit": "Créer", - "xpack.enterpriseSearch.content.engines.createEngine.technicalPreviewCallOut.title": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera au maximum de corriger tout problème, mais les fonctionnalités en version d'évaluation technique ne sont pas soumises aux accords de niveau de service d'assistance des fonctionnalités officielles en disponibilité générale.", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "Découvrez comment créer votre première application de recherche.", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "Créer votre première application de recherche", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "Erreur lors de la mise à jour du moteur", @@ -13142,7 +13141,6 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "Si vous synchronisez au moins un document avant d'avoir finalisé votre client connecteur, vous devrez recréer votre index de recherche.", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "Le format JSON n'est pas valide", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "Règles avancées", - "xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description": "Votre connecteur est configuré et déployé. Configurez une synchronisation unique en cliquant sur le bouton Sync, ou activez un calendrier de synchronisation récurrent. Le connecteur utilise le fuseau horaire UTC. ", "xpack.enterpriseSearch.content.indices.connectorScheduling.error.title": "Examinez la configuration de votre connecteur pour voir si des erreurs ont été signalées.", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.button.label": "Configurer", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.description": "Configurez et déployez votre connecteur, puis revenez ici pour définir votre calendrier de synchronisation. Ce calendrier déterminera l'intervalle de synchronisation du connecteur avec votre source de données pour les documents mis à jour.", @@ -21221,7 +21219,6 @@ "xpack.lens.xyChart.iconSelect.starLabel": "Étoile", "xpack.lens.xyChart.iconSelect.tagIconLabel": "Balise", "xpack.lens.xyChart.iconSelect.triangleIconLabel": "Triangle", - "xpack.lens.xyChart.ignoreGlobalFilters": "Utiliser les filtres globaux", "xpack.lens.xyChart.layerAnnotation": "Annotation", "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "Calques ignorant les filtres globaux", "xpack.lens.xyChart.layerAnnotationsLabel": "Annotations", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bf508c0c07d28..314f187038e2d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12979,7 +12979,6 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "検索アプリケーションの名前を指定", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "インデックスを選択", "xpack.enterpriseSearch.content.engines.createEngine.submit": "作成", - "xpack.enterpriseSearch.content.engines.createEngine.technicalPreviewCallOut.title": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticは最善の努力を講じてすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "最初の検索アプリケーションの作成を説明します。", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "最初の検索アプリケーションを作成", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "エンジンの更新エラー", @@ -13141,7 +13140,6 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "コネクタークライアントを確定する前に、1つ以上のドキュメントを同期した場合、検索インデックスを再作成する必要があります。", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON形式が無効です", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "詳細ルール", - "xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description": "コネクターが構成され、デプロイされます。[同期]ボタンをクリックしてワンタイム同期を構成するか、繰り返し同期スケジュールを有効にします。コネクターはタイムゾーンとしてUTCを使用します。", "xpack.enterpriseSearch.content.indices.connectorScheduling.error.title": "報告されたエラーのコネクター構成を確認します。", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.button.label": "構成", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.description": "コネクターを構成してデプロイし、ここに戻って、同期スケジュールを設定します。このスケジュールは、コネクターが更新されたドキュメントのデータソースと同期する間隔を指定します。", @@ -21221,7 +21219,6 @@ "xpack.lens.xyChart.iconSelect.starLabel": "星", "xpack.lens.xyChart.iconSelect.tagIconLabel": "タグ", "xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形", - "xpack.lens.xyChart.ignoreGlobalFilters": "グローバルフィルターを使用", "xpack.lens.xyChart.layerAnnotation": "注釈", "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "グローバルフィルターを無視するレイヤー", "xpack.lens.xyChart.layerAnnotationsLabel": "注釈", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5b54d8759e3f8..3586f89017cac 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12979,7 +12979,6 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "命名搜索应用程序", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "选择索引", "xpack.enterpriseSearch.content.engines.createEngine.submit": "创建", - "xpack.enterpriseSearch.content.engines.createEngine.technicalPreviewCallOut.title": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将尽最大努力来修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "下面我们指导您创建首个搜索应用程序。", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "创建您的首个搜索应用程序", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "更新引擎时出错", @@ -13141,7 +13140,6 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "如果您在完成连接器客户端之前至少同步一个文档,您必须重新创建搜索索引。", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON 格式无效", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "高级规则", - "xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description": "已配置并部署您的连接器。通过单击“同步”按钮配置一次性同步,或启用定期同步计划。连接器使用 UTC 作为其时区。", "xpack.enterpriseSearch.content.indices.connectorScheduling.error.title": "复查您的连接器配置以获取报告的错误。", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.button.label": "配置", "xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.description": "配置并部署连接器,然后返回此处设置同步计划。此计划将指示连接器与您的数据源进行同步以获取已更新文档的时间间隔。", @@ -21221,7 +21219,6 @@ "xpack.lens.xyChart.iconSelect.starLabel": "五角星", "xpack.lens.xyChart.iconSelect.tagIconLabel": "标签", "xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形", - "xpack.lens.xyChart.ignoreGlobalFilters": "使用全局筛选", "xpack.lens.xyChart.layerAnnotation": "标注", "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "忽略全局筛选的图层", "xpack.lens.xyChart.layerAnnotationsLabel": "标注", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index f5245cd85fb53..bf2a144921d9c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -25,6 +25,7 @@ import { RuleActionFrequency, RuleActionParam, } from '@kbn/alerting-plugin/common'; +import { v4 as uuidv4 } from 'uuid'; import { betaBadgeProps } from './beta_badge_props'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; import { @@ -242,6 +243,7 @@ export const ActionForm = ({ group: defaultActionGroupId, params: {}, frequency: defaultRuleFrequency, + uuid: uuidv4(), }); setActionIdByIndex(actionTypeConnectors[0].id, actions.length - 1); } else { @@ -255,6 +257,7 @@ export const ActionForm = ({ group: defaultActionGroupId, params: {}, frequency: DEFAULT_FREQUENCY, + uuid: uuidv4(), }); setActionIdByIndex(actionTypeConnectors[0].id, actions.length - 1); } @@ -427,7 +430,7 @@ export const ActionForm = ({ actionItem={actionItem} actionConnector={actionConnector} index={index} - key={`action-form-action-at-${index}`} + key={`action-form-action-at-${actionItem.uuid}`} setActionParamsProperty={setActionParamsProperty} setActionFrequencyProperty={setActionFrequencyProperty} setActionAlertsFilterProperty={setActionAlertsFilterProperty} diff --git a/x-pack/test/functional/apps/lens/group2/layer_actions.ts b/x-pack/test/functional/apps/lens/group2/layer_actions.ts index aec1efeebb454..1afda462402c3 100644 --- a/x-pack/test/functional/apps/lens/group2/layer_actions.ts +++ b/x-pack/test/functional/apps/lens/group2/layer_actions.ts @@ -50,6 +50,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.getVisibleText('lnsChangeIndexPatternSamplingInfo')).to.be('1%'); }); + it('should expose the ignore global filters control for a data layer', async () => { + await PageObjects.lens.openLayerContextMenu(); + expect( + await testSubjects.exists('lns-layerPanel-0 > lnsChangeIndexPatternIgnoringFilters') + ).to.be(false); + // click on open layer settings + await testSubjects.click('lnsLayerSettings'); + // annotations settings have only ignore filters + await testSubjects.click('lns-layerSettings-ignoreGlobalFilters'); + expect( + await testSubjects.exists('lns-layerPanel-0 > lnsChangeIndexPatternIgnoringFilters') + ).to.be(true); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + it('should add an annotation layer and settings shoud be available with ignore filters', async () => { // configure a date histogram await PageObjects.lens.configureDimension({ @@ -66,15 +81,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // add annotation layer await PageObjects.lens.createLayer('annotations'); - expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + expect( + await testSubjects.exists('lns-layerPanel-1 > lnsChangeIndexPatternIgnoringFilters') + ).to.be(true); await PageObjects.lens.openLayerContextMenu(1); await testSubjects.click('lnsLayerSettings'); // annotations settings have only ignore filters - await testSubjects.click('lnsXY-layerSettings-ignoreGlobalFilters'); + await testSubjects.click('lns-layerSettings-ignoreGlobalFilters'); // now close the panel and check the dataView picker has no icon await testSubjects.click('lns-indexPattern-dimensionContainerBack'); - expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(false); + expect( + await testSubjects.exists('lns-layerPanel-1 > lnsChangeIndexPatternIgnoringFilters') + ).to.be(false); }); it('should add a new visualization layer and disable the sampling if max operation is chosen', async () => { @@ -118,6 +137,29 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('lns-indexPattern-dimensionContainerBack'); }); + it('should expose sampling and ignore filters settings for reference lines layer', async () => { + await PageObjects.lens.createLayer('referenceLine'); + + await PageObjects.lens.openLayerContextMenu(3); + // click on open layer settings + await testSubjects.click('lnsLayerSettings'); + // random sampling available + await testSubjects.existOrFail('lns-indexPattern-random-sampling-row'); + // tweak the value + await PageObjects.lens.dragRangeInput('lns-indexPattern-random-sampling-slider', 2, 'left'); + // annotations settings have only ignore filters + await testSubjects.click('lns-layerSettings-ignoreGlobalFilters'); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + // Check both sampling and ignore filters are now present + await testSubjects.existOrFail('lnsChangeIndexPatternSamplingInfo'); + expect( + await testSubjects.getVisibleText('lns-layerPanel-3 > lnsChangeIndexPatternSamplingInfo') + ).to.be('1%'); + expect( + await testSubjects.exists('lns-layerPanel-3 > lnsChangeIndexPatternIgnoringFilters') + ).to.be(true); + }); + it('should switch to pie chart and have layer settings available', async () => { await PageObjects.lens.switchToVisualization('pie'); await PageObjects.lens.openLayerContextMenu(); diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts index 3778e3a6a79e1..c3158240665b2 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts @@ -126,5 +126,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); }); }); + + it('should bring the ignore global filters configured at series level over', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('mtrVis'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); + + it('should bring the ignore global filters configured at panel level over', async () => { + await visualBuilder.clickPanelOptions('gauge'); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('mtrVis'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts index f4bb52b9ebb51..860c812ba2fd5 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts @@ -127,5 +127,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); }); }); + + it('should bring the ignore global filters configured at series level over', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('mtrVis'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); + + it('should bring the ignore global filters configured at panel level over', async () => { + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('mtrVis'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts index 9d3112fc59374..0e6d270375d6e 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts @@ -217,5 +217,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); }); }); + + it('should bring the ignore global filters configured at panel level over', async () => { + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/timeseries.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/timeseries.ts index 32161cc25225f..c2adbeb99d7d7 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/timeseries.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/timeseries.ts @@ -187,5 +187,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await dimensions[2].getVisibleText()).to.eql('Top 10 values of extension.raw'); }); }); + + it('should bring the ignore global filters configured at series level over', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); + + it('should bring the ignore global filters configured at panel level over', async () => { + await visualBuilder.clickPanelOptions('timeSeries'); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/top_n.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/top_n.ts index 5ceee668b9586..4ca611c807b4a 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/top_n.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/top_n.ts @@ -179,5 +179,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); }); + + it('should bring the ignore global filters configured at series level over', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); + + it('should bring the ignore global filters configured at panel level over', async () => { + await visualBuilder.clickPanelOptions('topN'); + await visualBuilder.setIgnoreFilters(true); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts index e404eb3e451e6..90ac399d81cd5 100644 --- a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts +++ b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts @@ -74,7 +74,8 @@ export default function ({ getService }: FtrProviderContext) { }); }); - describe('for ML power user', () => { + // FLAKY: https://github.com/elastic/kibana/issues/159283 + describe.skip('for ML power user', () => { before(async () => { await ml.securityUI.loginAsMlPowerUser(); await ml.navigation.navigateToTrainedModels(); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 273c39adbf431..e475e8e646450 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -85,6 +85,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('rulesTab'); }); + it('should delete the right action when the same action has been added twice', async () => { + // create a new rule + const ruleName = generateUniqueKey(); + await rules.common.defineIndexThresholdAlert(ruleName); + + // create webhook connector + await testSubjects.click('.webhook-alerting-ActionTypeSelectOption'); + await testSubjects.click('createActionConnectorButton-0'); + await testSubjects.setValue('nameInput', 'webhook-test'); + await testSubjects.setValue('webhookUrlText', 'https://test.test'); + await testSubjects.setValue('webhookUserInput', 'fakeuser'); + await testSubjects.setValue('webhookPasswordInput', 'fakepassword'); + + // save rule + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + await find.setValueByClass('kibanaCodeEditor', 'myUniqueKey'); + await testSubjects.click('saveRuleButton'); + + // add new action and remove first one + await testSubjects.click('ruleSidebarEditAction'); + await testSubjects.click('.webhook-alerting-ActionTypeSelectOption'); + await find.clickByCssSelector( + '[data-test-subj="alertActionAccordion-0"] [aria-label="Delete"]' + ); + + // check that the removed action is the right one + const doesExist = await find.existsByXpath(".//*[text()='myUniqueKey']"); + expect(doesExist).to.eql(false); + + // clean up created alert + const alertsToDelete = await getAlertsByName(ruleName); + await deleteAlerts(alertsToDelete.map((rule: { id: string }) => rule.id)); + expect(true).to.eql(true); + }); + it('should create an alert', async () => { const alertName = generateUniqueKey(); await rules.common.defineIndexThresholdAlert(alertName); diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts index f1ee3ec7d981d..cfb01154be854 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts @@ -13,12 +13,19 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { getExceptionListItemResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { + getCreateExceptionListItemMinimalSchemaMock, + getCreateExceptionListItemSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } from '../../utils'; +import { + deleteAllExceptions, + removeExceptionListItemServerGeneratedProperties, + removeExceptionListServerGeneratedProperties, +} from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -31,6 +38,192 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); + describe('regressions', () => { + it('updates an item via its item_id without side effects', async () => { + // create a simple exception list + await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + const { body: items } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(items.total).to.eql(1); + const [item] = items.data; + const expectedId = item.id; + const expectedItemId = item.item_id; + + // update an exception list item's name, specifying its item_id + const updatePayload: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + await supertest + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatePayload) + .expect(200); + + const { body: itemsAfterUpdate } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Validate that we have a single exception item with the expected properties + expect(itemsAfterUpdate.total).to.eql(1); + const [updatedItem] = itemsAfterUpdate.data; + expect(updatedItem.name).to.eql('some other name'); + expect(updatedItem.id?.length).to.be.greaterThan(0); + expect(updatedItem.id).to.equal(expectedId); + expect(updatedItem.item_id?.length).to.be.greaterThan(0); + expect(updatedItem.item_id).to.equal(expectedItemId); + }); + + it('updates an item via its id without side effects', async () => { + // create a simple exception list + await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + const { body: items } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(items.total).to.eql(1); + const [item] = items.data; + const expectedId = item.id; + const expectedItemId = item.item_id; + + // update an exception list item's name, specifying its id + const { item_id: _, ...updateItemWithoutItemId } = + getUpdateMinimalExceptionListItemSchemaMock(); + const updatePayload: UpdateExceptionListItemSchema = { + ...updateItemWithoutItemId, + name: 'some other name', + id: expectedId, + }; + await supertest + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatePayload) + .expect(200); + + const { body: itemsAfterUpdate } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Validate that we have a single exception item with the expected properties + expect(itemsAfterUpdate.total).to.eql(1); + const [updatedItem] = itemsAfterUpdate.data; + expect(updatedItem.name).to.eql('some other name'); + expect(updatedItem.id?.length).to.be.greaterThan(0); + expect(updatedItem.id).to.equal(expectedId); + expect(updatedItem.item_id?.length).to.be.greaterThan(0); + expect(updatedItem.item_id).to.equal(expectedItemId); + }); + + it('preserves optional fields that are unspecified in the request, a la PATCH semantics', async () => { + // create a simple exception list + await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + const { meta, ...createPayload } = { + ...getCreateExceptionListItemSchemaMock(), + comments: [ + { + comment: 'Im an old comment', + }, + ], + }; + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(createPayload) + .expect(200); + + const { body: items } = await supertest + .get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${createPayload.list_id}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(items.total).to.eql(1); + const [item] = items.data; + + // Perform an update with only required fields. If any fields change on the item, then they're not really optional. + const { item_id: _, ...updatePayload } = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + id: item.id, + }; + + await supertest + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatePayload) + .expect(200); + + const { body: itemsAfterUpdate } = await supertest + .get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${createPayload.list_id}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Validate that we have a single exception item with the expected properties + expect(itemsAfterUpdate.total).to.eql(1); + const [updatedItem] = itemsAfterUpdate.data; + expect(updatedItem.id).to.eql(item.id); + expect(removeExceptionListItemServerGeneratedProperties(updatedItem)).to.eql( + removeExceptionListItemServerGeneratedProperties(item) + ); + }); + }); + it('should update a single exception list item property of name using an id', async () => { // create a simple exception list await supertest diff --git a/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts b/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts index dcbe7a86bf353..d7495dfd80f8d 100644 --- a/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts +++ b/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts @@ -12,7 +12,13 @@ export class AuditTrailTestPlugin implements Plugin { const router = core.http.createRouter(); router.get({ path: '/audit_log', validate: false }, async (context, request, response) => { const soClient = (await context.core).savedObjects.client; - await soClient.create('dashboard', {}); + await soClient.create('dashboard', { + title: '', + optionsJSON: '', + description: '', + panelsJSON: '{}', + kibanaSavedObjectMeta: {}, + }); await soClient.find({ type: 'dashboard' }); return response.noContent(); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/index.ts index 1513f5753779f..dabd799e996a9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index.ts @@ -9,7 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless common API', function () { - loadTestFile(require.resolve('./switch_project')); loadTestFile(require.resolve('./security_users')); loadTestFile(require.resolve('./spaces')); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/switch_project.ts b/x-pack/test_serverless/api_integration/test_suites/common/switch_project.ts deleted file mode 100644 index fe53e854a068f..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/switch_project.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const svlCommonApi = getService('svlCommonApi'); - const supertest = getService('supertest'); - - describe('switch_project', function () { - it('rejects request with invalid body', async () => { - const { body, status } = await supertest - .post(`/internal/serverless/switch_project`) - .set(svlCommonApi.getCommonRequestHeader()) - .send({ invalid: 'body' }); - - // in a non-serverless environment this would return a 404 - svlCommonApi.assertResponseStatusCode(400, status, body); - }); - }); -} diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index 35bceb3c20e4a..2ef117c4eca55 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -9,6 +9,7 @@ import { pageObjects as xpackFunctionalPageObjects } from '../../../test/functional/page_objects'; import { SvlCommonPageProvider } from './svl_common_page'; +import { SvlObltOnboardingPageProvider } from './svl_oblt_onboarding_page'; import { SvlObltOverviewPageProvider } from './svl_oblt_overview_page'; import { SvlSearchLandingPageProvider } from './svl_search_landing_page'; import { SvlSecLandingPageProvider } from './svl_sec_landing_page'; @@ -17,6 +18,7 @@ export const pageObjects = { ...xpackFunctionalPageObjects, svlCommonPage: SvlCommonPageProvider, + svlObltOnboardingPage: SvlObltOnboardingPageProvider, svlObltOverviewPage: SvlObltOverviewPageProvider, svlSearchLandingPage: SvlSearchLandingPageProvider, svlSecLandingPage: SvlSecLandingPageProvider, diff --git a/x-pack/test_serverless/functional/page_objects/svl_oblt_onboarding_page.ts b/x-pack/test_serverless/functional/page_objects/svl_oblt_onboarding_page.ts new file mode 100644 index 0000000000000..d7da6f4055c98 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/svl_oblt_onboarding_page.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SvlObltOnboardingPageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertSkipButtonExists() { + await testSubjects.existOrFail('obltOnboardingHomeSkipButton'); + }, + + async skipOnboarding() { + await testSubjects.click('obltOnboardingHomeSkipButton'); + await testSubjects.missingOrFail('obltOnboardingHomeSkipButton'); + }, + }; +} diff --git a/x-pack/test_serverless/functional/page_objects/svl_oblt_overview_page.ts b/x-pack/test_serverless/functional/page_objects/svl_oblt_overview_page.ts index d8eb4268ce393..08dcc51aeea33 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_oblt_overview_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_oblt_overview_page.ts @@ -11,6 +11,10 @@ export function SvlObltOverviewPageProvider({ getService }: FtrProviderContext) const testSubjects = getService('testSubjects'); return { + async assertPageHeaderExists() { + await testSubjects.existOrFail('obltOverviewPageHeader'); + }, + async assertAlertsSectionExists() { await testSubjects.existOrFail('obltOverviewAlerts'); }, diff --git a/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts b/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts index 31b6c87b12176..a028626f8a11d 100644 --- a/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts @@ -19,7 +19,7 @@ export function SvlObltNavigationServiceProvider({ async navigateToLandingPage() { await retry.tryForTime(60 * 1000, async () => { await PageObjects.common.navigateToApp('landingPage'); - await testSubjects.existOrFail('obltOverviewPageHeader', { timeout: 2000 }); + await testSubjects.existOrFail('obltOnboardingHomeTitle', { timeout: 2000 }); }); }, }; diff --git a/x-pack/test_serverless/functional/services/svl_search_navigation.ts b/x-pack/test_serverless/functional/services/svl_search_navigation.ts index 34c6864e7753c..dd0c1515a429c 100644 --- a/x-pack/test_serverless/functional/services/svl_search_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_search_navigation.ts @@ -12,14 +12,15 @@ export function SvlSearchNavigationServiceProvider({ getPageObjects, }: FtrProviderContext) { const retry = getService('retry'); - const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common']); return { async navigateToLandingPage() { await retry.tryForTime(60 * 1000, async () => { await PageObjects.common.navigateToApp('landingPage'); - await testSubjects.existOrFail('svlSearchOverviewPage', { timeout: 2000 }); + // The getting started page is currently empty, so there's nothing we could + // assert on. Once something exists here, we should add back a check. + // await testSubjects.existOrFail('svlSearchOverviewPage', { timeout: 2000 }); }); }, }; diff --git a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts index 965eb2f551cab..e281c15fafc09 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts @@ -8,12 +8,19 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { + const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage'); const svlObltOverviewPage = getPageObject('svlObltOverviewPage'); const svlObltNavigation = getService('svlObltNavigation'); describe('landing page', function () { - it('has alerts section', async () => { + it('has button to skip onboarding', async () => { await svlObltNavigation.navigateToLandingPage(); + await svlObltOnboardingPage.assertSkipButtonExists(); + }); + + it('skips onboarding', async () => { + await svlObltOnboardingPage.skipOnboarding(); + await svlObltOverviewPage.assertPageHeaderExists(); await svlObltOverviewPage.assertAlertsSectionExists(); }); });