diff --git a/.backportrc.json b/.backportrc.json index db7ad3b0eb887..94c2549418f17 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "repoName": "kibana", "targetBranchChoices": [ "main", + "8.5", "8.4", "8.3", "8.2", @@ -41,7 +42,7 @@ "backport" ], "branchLabelMapping": { - "^v8.5.0$": "main", + "^v8.6.0$": "main", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.buildkite/pipelines/artifacts.yml b/.buildkite/pipelines/artifacts.yml index b6d3cc9fc9b14..e133c4b569317 100644 --- a/.buildkite/pipelines/artifacts.yml +++ b/.buildkite/pipelines/artifacts.yml @@ -75,6 +75,7 @@ steps: label: 'Cloud Deployment' soft_fail: - exit_status: 255 + - exit_status: -1 agents: queue: n2-2 timeout_in_minutes: 30 diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index 59cac2eaf5259..617a80524e6dd 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -90,7 +90,7 @@ const uploadPipeline = (pipelineContent: string | object) => { } if ( - (await doAnyChangesMatch([/^x-pack\/plugins\/apm/])) || + (await doAnyChangesMatch([/^x-pack\/plugins\/apm/, /^packages\/kbn-apm-synthtrace/])) || GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c78de33572f14..93cf5cc23dde2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -937,6 +937,7 @@ packages/kbn-utility-types-jest @elastic/kibana-operations packages/kbn-utils @elastic/kibana-operations packages/kbn-yarn-lock-validator @elastic/kibana-operations packages/shared-ux/avatar/solution @elastic/shared-ux +packages/shared-ux/avatar/user_profile/impl @elastic/shared-ux packages/shared-ux/button_toolbar @elastic/shared-ux packages/shared-ux/button/exit_full_screen/impl @elastic/shared-ux packages/shared-ux/button/exit_full_screen/mocks @elastic/shared-ux @@ -966,6 +967,9 @@ packages/shared-ux/page/solution_nav @elastic/shared-ux packages/shared-ux/prompt/no_data_views/impl @elastic/shared-ux packages/shared-ux/prompt/no_data_views/mocks @elastic/shared-ux packages/shared-ux/prompt/no_data_views/types @elastic/shared-ux +packages/shared-ux/router/impl @elastic/shared-ux +packages/shared-ux/router/mocks @elastic/shared-ux +packages/shared-ux/router/types @elastic/shared-ux packages/shared-ux/storybook/config @elastic/shared-ux packages/shared-ux/storybook/mock @elastic/shared-ux x-pack/packages/ml/agg_utils @elastic/ml-ui diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index bdf14bf19ae84..880dc0b122f6a 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: 2022-09-21 +date: 2022-09-22 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 1dbd552804863..69f9463fb9e2e 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: 2022-09-21 +date: 2022-09-22 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 16ee88902efc4..fd4f21939b6e8 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 3a59a9f336d06..da826f60ffc9c 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 1ad2876e6274d..42d291bc57f02 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -196,7 +196,7 @@ "APMPluginSetupDependencies", ") => { config$: ", "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedServiceMetrics: boolean; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }>>; getApmIndices: () => Promise<", "ApmIndicesConfig", @@ -415,7 +415,7 @@ "label": "config", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedServiceMetrics: boolean; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }" ], @@ -811,7 +811,7 @@ "label": "APMConfig", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedServiceMetrics: boolean; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }" ], @@ -5618,7 +5618,7 @@ "description": [], "signature": [ "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedServiceMetrics: boolean; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }>>" ], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 936fb386563a2..bbd29cd9d6bdf 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 9f93e0cf30bad..7f66666be6ed8 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: 2022-09-21 +date: 2022-09-22 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 03ef60650c287..2667ba38a0f73 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: 2022-09-21 +date: 2022-09-22 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 e1070b82be030..4661b73c4cef5 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 9c1f56437d048..2601c456b45e1 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 496b2c6c79551..806f99fc1dd39 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: 2022-09-21 +date: 2022-09-22 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 58a63e62dd73a..0a73a5a20eec9 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index f80478c49dede..8cb4e1537763e 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: 2022-09-21 +date: 2022-09-22 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 8e2501623ac24..e2848bff600e0 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: 2022-09-21 +date: 2022-09-22 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 5d4888478dedf..b99308ac7643d 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 51aa262c87e5a..4427f251a9ef3 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.mdx b/api_docs/core.mdx index a5cd7b55e96a4..413a21a58e12e 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index cc8febab951ca..db608cbbc805b 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index a1e2f06b410e5..8a21209e990fb 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index ed8980fc7c838..ef500bb63d28d 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index f2850615238c6..d8e572383857c 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -11419,11 +11419,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" }, { "plugin": "stackAlerts", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index cb7c49f026491..3a8209067815f 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: 2022-09-21 +date: 2022-09-22 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 dfc44a7ebf77d..661eef503c692 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: 2022-09-21 +date: 2022-09-22 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 fa49792d2181c..4801005b8e955 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: 2022-09-21 +date: 2022-09-22 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 15d7ddfaff816..bec2047cd26a0 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: 2022-09-21 +date: 2022-09-22 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 cd3ddbce16f08..a07ba6097b88f 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: 2022-09-21 +date: 2022-09-22 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 706182af7954b..4619af2e25aa8 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: 2022-09-21 +date: 2022-09-22 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 95d0e05ecf2ab..7a8fd54baad3a 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: 2022-09-21 +date: 2022-09-22 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 73e19b646e6f6..e7c4937c65f86 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: 2022-09-21 +date: 2022-09-22 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 26c1133aa166d..c834b151512de 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 33ca134222c20..5b36d18efade8 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -690,7 +690,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch) | - | -| | [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | +| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | | | [expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx#:~:text=fieldFormats) | - | | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a7dc1bdec8678..7d2110b109555 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 08b245fb26f43..cb50c61bce67d 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: 2022-09-21 +date: 2022-09-22 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 22482b37187c2..ad00f12b57cf8 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: 2022-09-21 +date: 2022-09-22 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 e29728560aab4..694a717b00ea4 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 2f5fbce453b35..cd86fe18bd9c8 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 57604fa09d9cc..a3ea1b1190f83 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: 2022-09-21 +date: 2022-09-22 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 10e8ab28d577a..08cb65de4ffb1 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: 2022-09-21 +date: 2022-09-22 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 d21f75ff234d8..69819eb7b668c 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: 2022-09-21 +date: 2022-09-22 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 799b9e84cc1fd..1e0dae9b242e7 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index b856ac4924147..32e9439e61b31 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: 2022-09-21 +date: 2022-09-22 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 12011c049c6f6..32bf0c0ec8138 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 04fb06c8c96d4..ce92668b511a0 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: 2022-09-21 +date: 2022-09-22 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 d0b10885cf2b7..7c7a8804ab79f 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: 2022-09-21 +date: 2022-09-22 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 de91bc5178131..1927d1ba583e9 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: 2022-09-21 +date: 2022-09-22 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 1cc9d08db5a90..50164ed6d0dc8 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: 2022-09-21 +date: 2022-09-22 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 9fcf50bff28ef..04246f0e5bd22 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: 2022-09-21 +date: 2022-09-22 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 bc16b14f88d03..6226327af0e0b 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: 2022-09-21 +date: 2022-09-22 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 7b72a487826e6..6240f18c0ce55 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: 2022-09-21 +date: 2022-09-22 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 c219691750f84..633b28f5cb989 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: 2022-09-21 +date: 2022-09-22 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 6929d4d65ff1c..783dcdff3412b 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: 2022-09-21 +date: 2022-09-22 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 cf8447fa6e554..2e2068e4bd83b 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: 2022-09-21 +date: 2022-09-22 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 b86adf33d39e5..0950fff727a5f 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: 2022-09-21 +date: 2022-09-22 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 3fe265b6ba374..e9d5af42e5be2 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: 2022-09-21 +date: 2022-09-22 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 c017cb7f59f51..f83d943cbcd24 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: 2022-09-21 +date: 2022-09-22 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 8bd22f5dbc372..e186179b019e0 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: 2022-09-21 +date: 2022-09-22 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 fb97fac6d9a73..f917d8d7d9967 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: 2022-09-21 +date: 2022-09-22 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 02113ea2e591b..171e1798f6ecf 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: 2022-09-21 +date: 2022-09-22 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 3a28e7c492bf3..b89efdc3b0f69 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: 2022-09-21 +date: 2022-09-22 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 faee578b74688..69eb98792d527 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -2,7 +2,133 @@ "id": "files", "client": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "files", + "id": "def-public.FilesContext", + "type": "Function", + "tags": [], + "label": "FilesContext", + "description": [], + "signature": [ + "({ children }: { children?: React.ReactNode; }) => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesContext.$1", + "type": "Object", + "tags": [], + "label": "{ children }", + "description": [], + "signature": [ + "{ children?: React.ReactNode; }" + ], + "path": "x-pack/plugins/files/public/components/context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Image", + "type": "Function", + "tags": [ + "note" + ], + "label": "Image", + "description": [ + "\nA viewport-aware component that displays an image. This component is a very\nthin wrapper around the img tag.\n" + ], + "signature": [ + "React.ForwardRefExoticComponent<", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + " & React.RefAttributes>" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Image.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.UploadFile", + "type": "Function", + "tags": [], + "label": "UploadFile", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + ") => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/upload_file/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.UploadFile.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/upload_file/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [ { "parentPluginId": "files", @@ -62,7 +188,7 @@ "- create file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -97,7 +223,7 @@ "- delete file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -140,7 +266,7 @@ "- get file by ID args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -191,7 +317,7 @@ "- list files args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -234,7 +360,7 @@ "- update file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -252,7 +378,7 @@ "\nStream the contents of the file to Kibana server for storage.\n" ], "signature": [ - "(args: { body: unknown; } & { id: string; } & { kind: string; }) => Promise<{ ok: true; size: number; }>" + "(args: { body: unknown; } & { id: string; } & { selfDestructOnAbort?: boolean | undefined; } & { kind: string; } & { abortSignal?: AbortSignal | undefined; }) => Promise<{ ok: true; size: number; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -269,7 +395,7 @@ "- upload file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -304,7 +430,7 @@ "- download file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -398,7 +524,7 @@ "- File share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -433,7 +559,7 @@ "- File unshare arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -476,7 +602,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -527,7 +653,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -621,6 +747,270 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + " extends React.ImgHTMLAttributes" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.src", + "type": "string", + "tags": [], + "label": "src", + "description": [], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.alt", + "type": "string", + "tags": [], + "label": "alt", + "description": [], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onFirstVisible", + "type": "Function", + "tags": [], + "label": "onFirstVisible", + "description": [ + "\nEmits when the image first becomes visible" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [ + "\nUploadFile component props" + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.kind", + "type": "Uncategorized", + "tags": [], + "label": "kind", + "description": [ + "\nA file kind that should be registered during plugin startup. See {@link FileServiceStart}." + ], + "signature": [ + "Kind" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA files client that will be used process uploads." + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.FilesClient", + "text": "FilesClient" + } + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.allowClear", + "type": "CompoundType", + "tags": [ + "note" + ], + "label": "allowClear", + "description": [ + "\nAllow users to clear a file after uploading.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.immediate", + "type": "CompoundType", + "tags": [], + "label": "immediate", + "description": [ + "\nStart uploading the file as soon as it is provided\nby the user." + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.meta", + "type": "Object", + "tags": [], + "label": "meta", + "description": [ + "\nMetadata that you want to associate with any uploaded files" + ], + "signature": [ + "Record | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.allowRepeatedUploads", + "type": "CompoundType", + "tags": [ + "default" + ], + "label": "allowRepeatedUploads", + "description": [ + "\nWhether this component should display a \"done\" state after processing an\nupload or return to the initial state to allow for another upload.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onDone", + "type": "Function", + "tags": [], + "label": "onDone", + "description": [ + "\nCalled when the an upload process fully completes" + ], + "signature": [ + "(files: UploadedFile[]) => void" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onDone.$1", + "type": "Array", + "tags": [], + "label": "files", + "description": [], + "signature": [ + "UploadedFile[]" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onError", + "type": "Function", + "tags": [], + "label": "onError", + "description": [ + "\nCalled when an error occurs during upload" + ], + "signature": [ + "((e: Error) => void) | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onError.$1", + "type": "Object", + "tags": [], + "label": "e", + "description": [], + "signature": [ + "Error" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false } ], "enums": [], @@ -762,7 +1152,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit<{ body: unknown; } & { id: string; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ id: string; fileName?: string | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit<", + "; }>; upload: (arg: Omit<{ body: unknown; } & { id: string; } & { selfDestructOnAbort?: boolean | undefined; } & { kind: string; } & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ id: string; fileName?: string | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit<", { "pluginId": "files", "scope": "common", @@ -835,6 +1225,95 @@ } ], "objects": [], + "setup": { + "parentPluginId": "files", + "id": "def-public.FilesSetup", + "type": "Interface", + "tags": [], + "label": "FilesSetup", + "description": [ + "\nPublic setup-phase contract" + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.filesClientFactory", + "type": "Object", + "tags": [], + "label": "filesClientFactory", + "description": [ + "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKind}." + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.FilesClientFactory", + "text": "FilesClientFactory" + } + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.registerFileKind", + "type": "Function", + "tags": [], + "label": "registerFileKind", + "description": [ + "\nRegister a {@link FileKind} which allows for specifying details about the files\nthat will be uploaded.\n" + ], + "signature": [ + "(fileKind: ", + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, + ") => void" + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.registerFileKind.$1", + "type": "Object", + "tags": [], + "label": "fileKind", + "description": [ + "- the file kind to register" + ], + "signature": [ + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + } + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, "start": { "parentPluginId": "files", "id": "def-public.FilesStart", @@ -843,13 +1322,15 @@ "label": "FilesStart", "description": [], "signature": [ + "{ filesClientFactory: ", { "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", - "section": "def-public.FilesSetup", - "text": "FilesSetup" - } + "section": "def-public.FilesClientFactory", + "text": "FilesClientFactory" + }, + "; }" ], "path": "x-pack/plugins/files/public/plugin.ts", "deprecated": false, diff --git a/api_docs/files.mdx b/api_docs/files.mdx index bb3bd2bbcff56..ea2cf22087412 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,13 +21,19 @@ Contact [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 240 | 0 | 6 | 2 | +| 263 | 0 | 15 | 2 | ## Client +### Setup + + ### Start +### Functions + + ### Interfaces diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 07f8d74f9c842..deaa296249ab3 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index c5034da58e5a1..0aae391e045d8 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: 2022-09-21 +date: 2022-09-22 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 fe535e96c2df8..c1d48c0989a34 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: 2022-09-21 +date: 2022-09-22 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 1c29739eb6bb6..20dac194b7666 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 501b570f5d017..ea1e351b8eca7 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: 2022-09-21 +date: 2022-09-22 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 c01e2fd84fcd7..14dbc7f48fedb 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: 2022-09-21 +date: 2022-09-22 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 8a0e939a8ed2b..e9ee4c12f506d 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: 2022-09-21 +date: 2022-09-22 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 b0b1c0aba0443..83ec164aa79e6 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: 2022-09-21 +date: 2022-09-22 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 32e01d93df529..475c42ddf450b 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: 2022-09-21 +date: 2022-09-22 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 376fd853d73aa..efdce690d2247 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: 2022-09-21 +date: 2022-09-22 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 dc1ee35a57b69..26ee28f0d5116 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: 2022-09-21 +date: 2022-09-22 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 b87509e3cc43f..047d85e585bb0 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 0d305dac2e069..711dca1d69319 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 873c79fa16586..f2d1444eddc39 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: 2022-09-21 +date: 2022-09-22 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 a4f4547481fd4..c4b2f07e35477 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: 2022-09-21 +date: 2022-09-22 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 382a8ad33ef27..66f9a16b7e217 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: 2022-09-21 +date: 2022-09-22 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 e1eccc8fdb940..3ec92012afe72 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: 2022-09-21 +date: 2022-09-22 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 dac62dbfa13f6..6081a36a42fdc 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: 2022-09-21 +date: 2022-09-22 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 2f3a2e4b6e791..ffb73d2b3cbbc 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 948d34bfbcbd4..145d493a2664f 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: 2022-09-21 +date: 2022-09-22 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 92e27ecd44c61..8670cc5eb9306 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 5901d75a22dfe..791dbb286d079 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: 2022-09-21 +date: 2022-09-22 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 3d141045316e9..c240caf2e87dd 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index c4bf743890b27..6e1955e64d291 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: 2022-09-21 +date: 2022-09-22 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 e80b8d319443f..548d3a429b221 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: 2022-09-21 +date: 2022-09-22 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 dcf4e1168cda0..f71fdfc630e12 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: 2022-09-21 +date: 2022-09-22 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 b0d2e856b602b..6a78799b95ef2 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: 2022-09-21 +date: 2022-09-22 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 af88a7a61ecaf..92b6b367bc9ff 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index d179dd6f56b8d..235779d14f01d 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: 2022-09-21 +date: 2022-09-22 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 610cf11db4ce2..754221c503859 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: 2022-09-21 +date: 2022-09-22 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 9796cc2ef31a2..85bc4f51eec07 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: 2022-09-21 +date: 2022-09-22 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 be242a54c53a0..cb3f886dd7e36 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index d36ee3d0a04b8..f9b256a70ba39 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 92f03112a75e2..6ffaa576b57fe 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: 2022-09-21 +date: 2022-09-22 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 5ee4fe180b1a6..3966c5e685595 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: 2022-09-21 +date: 2022-09-22 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 ddab8a6da8ba5..de23002284e5c 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: 2022-09-21 +date: 2022-09-22 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 3cf2b1c781613..c4777e8cf10ee 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: 2022-09-21 +date: 2022-09-22 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 1978fd217005e..f89a0fcf9a39a 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: 2022-09-21 +date: 2022-09-22 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 f921a117aa1ac..9c41e734fcb99 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: 2022-09-21 +date: 2022-09-22 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 27c1ae53f78c0..0cea1dc774c2a 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: 2022-09-21 +date: 2022-09-22 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 2fef5d62c1be4..cfae804acde4e 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: 2022-09-21 +date: 2022-09-22 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 fd1a6a7790662..8de77b355c88a 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: 2022-09-21 +date: 2022-09-22 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 e323813393c86..9cb6865cfc80d 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: 2022-09-21 +date: 2022-09-22 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 8e7baa413c2d9..21a8ec5b98cf7 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: 2022-09-21 +date: 2022-09-22 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 37560d75caaa7..30982dcb656d0 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: 2022-09-21 +date: 2022-09-22 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_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index b3de07059b8b3..9e5b655ab4d8f 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: 2022-09-21 +date: 2022-09-22 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 bccffa5f3169b..2ae5b314e3ab7 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: 2022-09-21 +date: 2022-09-22 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 2d70af69f3207..5de0d422641c3 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: 2022-09-21 +date: 2022-09-22 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 92b87e2784eb8..f54426285ba57 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: 2022-09-21 +date: 2022-09-22 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 36b71dc6bbf18..3307668b79611 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: 2022-09-21 +date: 2022-09-22 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 8840046d85094..f5bb2de27d66d 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: 2022-09-21 +date: 2022-09-22 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 d829790191100..97d5a26685f3a 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: 2022-09-21 +date: 2022-09-22 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 ea0cddb465a58..ece7b5cdbf4fe 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: 2022-09-21 +date: 2022-09-22 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 47c38a7c769e9..2a4053b22f8f0 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: 2022-09-21 +date: 2022-09-22 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 28553cc4442de..66bc99d5a3e6a 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: 2022-09-21 +date: 2022-09-22 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 e3121dd71a4c5..a3818855fb04c 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: 2022-09-21 +date: 2022-09-22 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_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 51ab765a1ebfd..f9d1268dfac82 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: 2022-09-21 +date: 2022-09-22 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 2aca3e87366d5..22cd35d5bd484 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: 2022-09-21 +date: 2022-09-22 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 1de746416ab97..b4f237ef1b22f 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: 2022-09-21 +date: 2022-09-22 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 a8eba9d98e241..60166d8b9529b 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: 2022-09-21 +date: 2022-09-22 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 df85327dc4356..e133115b93e4e 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: 2022-09-21 +date: 2022-09-22 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 a90ff68ad7816..19749a36a463f 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: 2022-09-21 +date: 2022-09-22 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 8970c1fb4c739..a0793ce78d4cc 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: 2022-09-21 +date: 2022-09-22 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 ca61cd3b1df6c..ed3da76740aa1 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: 2022-09-21 +date: 2022-09-22 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 593f2f23e1172..f7b43d5d2e357 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: 2022-09-21 +date: 2022-09-22 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 1b6fc98e5aaa8..7554694a7e97e 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: 2022-09-21 +date: 2022-09-22 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 58d6dab3192fc..f4bde1777de85 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: 2022-09-21 +date: 2022-09-22 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 bdaea106a91d2..28b7898bfeacc 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: 2022-09-21 +date: 2022-09-22 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 82dfdc3bf2116..d6a769f16193d 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: 2022-09-21 +date: 2022-09-22 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 53c51781010a0..05d63bea9feff 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: 2022-09-21 +date: 2022-09-22 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 a8b0c0db5b795..0c7d773da623c 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: 2022-09-21 +date: 2022-09-22 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 aa122cf8c7e6e..14f381a2ebf56 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: 2022-09-21 +date: 2022-09-22 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 a22516bdc6e0e..0cfbea89cd0c5 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: 2022-09-21 +date: 2022-09-22 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 a51d321654147..741db5f261f88 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: 2022-09-21 +date: 2022-09-22 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 458d203e20aa7..0c91019f2e6b9 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: 2022-09-21 +date: 2022-09-22 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 cc444c3d5b8ca..ae8cf6e8b9770 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: 2022-09-21 +date: 2022-09-22 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 3bc9d80c72a18..d5db7822402e6 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: 2022-09-21 +date: 2022-09-22 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 d29a114050dbf..3825cd5ba57fe 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: 2022-09-21 +date: 2022-09-22 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 4509eb592b397..7e445c7f999d7 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: 2022-09-21 +date: 2022-09-22 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 af698072a94ca..77f5a3959fbe7 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: 2022-09-21 +date: 2022-09-22 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 eb5d40300ed87..901253890c7e9 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: 2022-09-21 +date: 2022-09-22 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 4cff04d98dc26..deaa4702f705c 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: 2022-09-21 +date: 2022-09-22 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 721ea0c3945d9..140d9132ec2c0 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: 2022-09-21 +date: 2022-09-22 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 415f15276d937..b0c9c4220e7f5 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: 2022-09-21 +date: 2022-09-22 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 6db848c711182..e3a0724e0dcb9 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: 2022-09-21 +date: 2022-09-22 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 573f8733da5b5..fcbeeaa6e9627 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: 2022-09-21 +date: 2022-09-22 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 b883779a793a0..f7102c3ec16e0 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: 2022-09-21 +date: 2022-09-22 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 d18c50836ba87..e60af9f5d9eab 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: 2022-09-21 +date: 2022-09-22 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_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 3d126a078dc71..e8d711e575cc6 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: 2022-09-21 +date: 2022-09-22 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 faa4e8f8e4e0d..5352c783a9b45 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: 2022-09-21 +date: 2022-09-22 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 d2614fafeaf5f..8e81de35d50fb 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: 2022-09-21 +date: 2022-09-22 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 9051a0bf0b52e..9fe6811754183 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: 2022-09-21 +date: 2022-09-22 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 c9419dbd572a9..a0cf2e4ffbfa1 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: 2022-09-21 +date: 2022-09-22 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 b1dba4af85762..56c0f34c4227e 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: 2022-09-21 +date: 2022-09-22 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 b67a590ecfa75..78b7a706f203d 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: 2022-09-21 +date: 2022-09-22 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 2dd6083d4293f..6965e9b7a4328 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: 2022-09-21 +date: 2022-09-22 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 ae5f69bf3d5bf..63c27ceafd0c2 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: 2022-09-21 +date: 2022-09-22 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 b16d6e10e8725..3064d37b2b764 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: 2022-09-21 +date: 2022-09-22 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.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index a1c0750d2f77b..fc9a42796ed91 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.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 c733e3cc061a7..c1c9212ced012 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: 2022-09-21 +date: 2022-09-22 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 cc3c1aae43229..676f01fc2cf5b 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: 2022-09-21 +date: 2022-09-22 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 f303a7a2bce56..c693554b5408d 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: 2022-09-21 +date: 2022-09-22 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_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 500ece47590d2..11c07ea3a463d 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: 2022-09-21 +date: 2022-09-22 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 34db9ddb92aa6..1d4b36eb97b55 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: 2022-09-21 +date: 2022-09-22 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 8736af0225f13..244a90780255a 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: 2022-09-21 +date: 2022-09-22 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 f1ff3c7382729..95b139093f28d 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: 2022-09-21 +date: 2022-09-22 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 9aebf6bfe65fa..0464c4b38c300 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: 2022-09-21 +date: 2022-09-22 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 d9ab077872da7..93cf2d5749ca3 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: 2022-09-21 +date: 2022-09-22 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 bd4671af281c4..225a82aa4be21 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: 2022-09-21 +date: 2022-09-22 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 22cc5dac4f43d..49bcb201c222a 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: 2022-09-21 +date: 2022-09-22 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 cfe0c1672a0ac..e114e2af0b6bc 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: 2022-09-21 +date: 2022-09-22 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 d6f58108da44a..a1ce5e75dfdad 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: 2022-09-21 +date: 2022-09-22 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 5aa0b5bf3138c..04163a37acbf9 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: 2022-09-21 +date: 2022-09-22 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 59e3b1e657613..4f794d1bce300 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: 2022-09-21 +date: 2022-09-22 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 a17faa06bcc22..11a6a77f6a8b1 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: 2022-09-21 +date: 2022-09-22 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 6b5aaa33c88c7..12bc341027894 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: 2022-09-21 +date: 2022-09-22 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 daf8186f94e4f..effecee9ba65b 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: 2022-09-21 +date: 2022-09-22 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 7a5eff7a76390..2e03ba6fcc3ed 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: 2022-09-21 +date: 2022-09-22 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 8c8a72aa73172..7fba986174a28 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: 2022-09-21 +date: 2022-09-22 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 ec24423b0c296..4c463966e38b2 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: 2022-09-21 +date: 2022-09-22 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_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index e6385a2a75836..e6d5f7d5a1811 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: 2022-09-21 +date: 2022-09-22 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 0c8abfaa31f78..bc67c83d3d8ae 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: 2022-09-21 +date: 2022-09-22 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 98ee357d65bf7..c02b5f7e8791c 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: 2022-09-21 +date: 2022-09-22 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_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index a5933775d7f23..8fcc6aac79dd3 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 48aef5d9e3a5a..853fc6b82e6f5 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 8f7fe0f6163f2..e53441bfc1185 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 738cbc10225ea..a17b5a1953925 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: 2022-09-21 +date: 2022-09-22 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 d4874ff125c9d..473834ecc1707 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: 2022-09-21 +date: 2022-09-22 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 6bf81edf60447..c9b9a87273772 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: 2022-09-21 +date: 2022-09-22 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 f631627c7c671..ef40536890d3c 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: 2022-09-21 +date: 2022-09-22 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 f8d2a00000ad6..d66e50e0cd43b 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index b2429c7efea6a..dd1720b80cd2a 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 6942d08a8a361..d0e65f194f45f 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: 2022-09-21 +date: 2022-09-22 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 80a176a289a2b..2c5eb3d91f286 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: 2022-09-21 +date: 2022-09-22 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 87fd03b5794a6..0ae0cd612b849 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: 2022-09-21 +date: 2022-09-22 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 f5c4c8c690179..301aa937001db 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: 2022-09-21 +date: 2022-09-22 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 f2562939d3214..91302dc2f4ad0 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index dca63daba9785..d2bdf0f51e9fe 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: 2022-09-21 +date: 2022-09-22 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 015215fa5039e..f323cae307882 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: 2022-09-21 +date: 2022-09-22 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 36d189e120cd8..82a4ce2a7aac4 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: 2022-09-21 +date: 2022-09-22 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 5900ab2e01897..5bac274913abd 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: 2022-09-21 +date: 2022-09-22 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 410b3389b9ef6..8686d9c3536a9 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: 2022-09-21 +date: 2022-09-22 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 250cb345b9a21..f20257486b238 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: 2022-09-21 +date: 2022-09-22 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 9e062fe408eec..f562a422f61aa 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: 2022-09-21 +date: 2022-09-22 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 448c81ec4b79a..9db2c460e004e 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: 2022-09-21 +date: 2022-09-22 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 3593f09e53c79..6906d65c39af0 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: 2022-09-21 +date: 2022-09-22 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 c1ea683699773..e825685ecdd41 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: 2022-09-21 +date: 2022-09-22 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 90c31ad1d6e85..375edb77fda06 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: 2022-09-21 +date: 2022-09-22 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_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 11ed64d22dcc7..dd58b41f7aebe 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: 2022-09-21 +date: 2022-09-22 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 5cb8a68077a19..61e989baa7c20 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: 2022-09-21 +date: 2022-09-22 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 8b8c6e62c6fc1..5c93551750307 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: 2022-09-21 +date: 2022-09-22 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 8c4774f561e62..799e715ef6f35 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: 2022-09-21 +date: 2022-09-22 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 729aa317ec334..8b1be60494682 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: 2022-09-21 +date: 2022-09-22 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 095a8324b79c3..93c0682c786c0 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: 2022-09-21 +date: 2022-09-22 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 bd961f3cd990a..31025c9fac7de 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: 2022-09-21 +date: 2022-09-22 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 71f72e035cb74..7ac444b7ef812 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: 2022-09-21 +date: 2022-09-22 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 657ac74105e43..f7a68e1a00588 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: 2022-09-21 +date: 2022-09-22 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 50887b10d7a0d..4dca929c1363c 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: 2022-09-21 +date: 2022-09-22 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 0c74acc01304f..b10ab2782c488 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: 2022-09-21 +date: 2022-09-22 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 8e592b7f85eee..90e9bca0ae863 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: 2022-09-21 +date: 2022-09-22 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 f0a28f0875ed9..d6e1d8246e70b 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 28faa9e162e77..5baa0d9007a3b 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: 2022-09-21 +date: 2022-09-22 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 46c98951488ae..04e8a2a5ad66d 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index aa7809f7eb2e1..6a636d981544d 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: 2022-09-21 +date: 2022-09-22 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 1843be33150fa..98354b22dd3f0 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: 2022-09-21 +date: 2022-09-22 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 17b48fee7a458..7812305c5e84d 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: 2022-09-21 +date: 2022-09-22 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 5df40a7588976..e66f3d2941b8d 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: 2022-09-21 +date: 2022-09-22 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 5340deeed61b7..b90ddfe887f5e 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index b8d975b8f5814..c48ba82c33d21 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -658,7 +658,7 @@ "label": "observability", "description": [], "signature": [ - "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; }" + "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsCommandReference: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 4590efe64c129..ea57e416a51d9 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: 2022-09-21 +date: 2022-09-22 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 e074495e15334..08d82611db4c2 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 79dcf4c12fdba..90d36c669fdac 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index bf0588c4f99c1..3309781c13f31 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: 2022-09-21 +date: 2022-09-22 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 7ae016edcfb59..866fc7615a3e7 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: 2022-09-21 +date: 2022-09-22 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 06994c389d718..5c9a1fe11d91a 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: 2022-09-21 +date: 2022-09-22 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 95466ada26028..9141040ca4c36 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: 2022-09-21 +date: 2022-09-22 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 7f2a5f8ffbca4..6ab866bd44dd1 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 94177dc5aa243..8310f90111603 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: 2022-09-21 +date: 2022-09-22 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 44625438cd032..822e97678f09b 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: 2022-09-21 +date: 2022-09-22 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_generate.mdx b/api_docs/kbn_generate.mdx index af968a0d6c12b..5f39a5ed34eb1 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index eb3041437f858..3277b35b8d4a1 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index ea945e927f03b..ef29d0fa81189 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: 2022-09-21 +date: 2022-09-22 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 5d79694335b6b..041357a77e22b 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 94506cf8d9909..20f96872d286b 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: 2022-09-21 +date: 2022-09-22 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 0d4b974929b83..cbe688254ef8d 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: 2022-09-21 +date: 2022-09-22 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 bf4c70817d7bc..38dd4fd120884 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index d051acb699996..6e0f1031ba7a6 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 7d0955caeb361..940a58f4154dc 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: 2022-09-21 +date: 2022-09-22 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 b6c9a79910594..e8b8fa5d82554 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: 2022-09-21 +date: 2022-09-22 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 4ed4996625614..fa7ba1ee6795a 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index e1edaaac04cc2..3181dee87f7db 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index b8e4bbc6d85d7..30f74050e31a9 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: 2022-09-21 +date: 2022-09-22 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 cdf7e9493bfde..bc64637c8f1a6 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: 2022-09-21 +date: 2022-09-22 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 3f07937de52fe..e6ead9f8174da 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: 2022-09-21 +date: 2022-09-22 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 bc63c5e56f04c..7003f5277fc39 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 20d206ee85420..a0ecdade3f9e6 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: 2022-09-21 +date: 2022-09-22 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_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index f03e2c0dd1368..32da79b32c407 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: 2022-09-21 +date: 2022-09-22 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index f5c6ec4dab20c..7a92565583d64 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 4a2b7d251d645..e108034ba2c2d 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 9dec2e2d12388..98b48440df6fd 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: 2022-09-21 +date: 2022-09-22 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 3ecb10b8ce84a..9d676c1c06397 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 629055ccc04e7..b4483ef89d466 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: 2022-09-21 +date: 2022-09-22 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 f2e631ec8cd4b..0e8047259ac54 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: 2022-09-21 +date: 2022-09-22 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 b7141c78f3cf9..725c12f183891 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 5a9f1942a5bd6..6f4a984b14a3a 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 0e6aab2d83377..5f00051849f1c 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index c4061ab88aac1..c1a782c141bc6 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index fd9731c4d32f5..d5e955d1f2595 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index ec574df2ac420..77bceec83858e 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: 2022-09-21 +date: 2022-09-22 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 060150aec0633..b7856995a8108 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: 2022-09-21 +date: 2022-09-22 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 1c47b09302c0e..bc4e23e2e983d 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: 2022-09-21 +date: 2022-09-22 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 343773fc6961f..82e3fb0ae468c 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: 2022-09-21 +date: 2022-09-22 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 5b6f8c7121eb2..a0d2052ae7794 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: 2022-09-21 +date: 2022-09-22 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 d91fd25457476..8117732d57ad9 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: 2022-09-21 +date: 2022-09-22 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 47970932a1793..c403a28913447 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 34e695f21aeea..a3ba38ac91210 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: 2022-09-21 +date: 2022-09-22 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 e58504680e2cf..e59220c83f958 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: 2022-09-21 +date: 2022-09-22 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 a87cfd6fe0973..b77cb1ff96d10 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: 2022-09-21 +date: 2022-09-22 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 ff535c6f560d9..c1d3390dd76b6 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: 2022-09-21 +date: 2022-09-22 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 5df5c3218e6a1..778caf257464d 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: 2022-09-21 +date: 2022-09-22 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 fc58053025b36..d326fdb690acd 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: 2022-09-21 +date: 2022-09-22 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 1af486fce1c5e..25471b3d51cd0 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: 2022-09-21 +date: 2022-09-22 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 b743139813871..dfbc5720c2a3a 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 78782a2742f93..793150d936933 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: 2022-09-21 +date: 2022-09-22 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_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 9d1ce452077a0..e1daf06b3b274 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: 2022-09-21 +date: 2022-09-22 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_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 86f562d4bb410..a4cf83db9324e 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: 2022-09-21 +date: 2022-09-22 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 534731ed2ed6c..8658ea40c1995 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: 2022-09-21 +date: 2022-09-22 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 3b9307fe75198..0dc7d8de76359 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: 2022-09-21 +date: 2022-09-22 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 d8a57acd59459..35914b6d9b59d 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: 2022-09-21 +date: 2022-09-22 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_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 29650e5ed8646..a2b9f784ff86e 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: 2022-09-21 +date: 2022-09-22 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_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 9962da3b57786..bb96516fa0fbd 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: 2022-09-21 +date: 2022-09-22 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 f5e9eec99917c..d804aea444daa 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: 2022-09-21 +date: 2022-09-22 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 2123307bc9e97..31a07313d1af3 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: 2022-09-21 +date: 2022-09-22 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 e09c5179542e1..4f57dbfd4399d 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: 2022-09-21 +date: 2022-09-22 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 17b290a187bd0..673c8bb7bb567 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: 2022-09-21 +date: 2022-09-22 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 0624865a7cb3e..135bb6bd1caad 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: 2022-09-21 +date: 2022-09-22 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 5a3f2d93ec15d..3fd6fad8f1a41 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: 2022-09-21 +date: 2022-09-22 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 158d91473e56c..6334f26246c3b 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: 2022-09-21 +date: 2022-09-22 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 8f4d8b0219143..e737f1156e683 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: 2022-09-21 +date: 2022-09-22 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 fe62493020308..196d53a7ba389 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: 2022-09-21 +date: 2022-09-22 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 c95da8b4cc92f..b49adcd6ea044 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: 2022-09-21 +date: 2022-09-22 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 655e11aa964bf..a25944fdb2882 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: 2022-09-21 +date: 2022-09-22 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 db40292785ab2..54f45ccf3da49 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: 2022-09-21 +date: 2022-09-22 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_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 5824ea5c73c58..7de045590c6f7 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: 2022-09-21 +date: 2022-09-22 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 8ec6570a0220e..f65e964f0848a 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: 2022-09-21 +date: 2022-09-22 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 d9814c190f09f..81fb8c51ac022 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: 2022-09-21 +date: 2022-09-22 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 6744b6d0e1dcd..8cb95096540e5 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: 2022-09-21 +date: 2022-09-22 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 c3d29d99df19d..23c2afad25bf7 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 80d98d2a35ebe..aae68b0778949 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index e149b42881f92..a0578b5514cd2 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 349e5920113d0..7e7f3747c59ee 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: 2022-09-21 +date: 2022-09-22 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 99f2f0c8664b2..5a733985a17a5 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: 2022-09-21 +date: 2022-09-22 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 8c97882f6aa61..f0012bc98f817 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: 2022-09-21 +date: 2022-09-22 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 83166d900d2ed..b7f2429457b19 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: 2022-09-21 +date: 2022-09-22 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 9230fac245d35..9dd5c24776896 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: 2022-09-21 +date: 2022-09-22 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 1e3817ccdee9f..5e84097dcef14 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: 2022-09-21 +date: 2022-09-22 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 53a5a76b85ff3..b6729e8555502 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 054c0285c637d..8d30e51ee9228 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index afa19269f8816..94e15ec19c237 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 8c4e404667da3..1abf7a1158dc8 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 805990c966ba5..0c95e31f10e68 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: 2022-09-21 +date: 2022-09-22 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_theme.mdx b/api_docs/kbn_ui_theme.mdx index 64654b10b3002..a6871ad14be19 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index e4d20bc32a22e..d996627374c01 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: 2022-09-21 +date: 2022-09-22 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 a2ab571bf69fa..2ceeeae778cfe 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: 2022-09-21 +date: 2022-09-22 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 0767b1a144f7e..4e1ba1953d065 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: 2022-09-21 +date: 2022-09-22 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 c937c340e4433..eb5beac7065ff 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: 2022-09-21 +date: 2022-09-22 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 64f290c135730..4667f30cbf1f4 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: 2022-09-21 +date: 2022-09-22 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 da1c2cc403976..34c0d11826ba1 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: 2022-09-21 +date: 2022-09-22 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 79b13332456d4..97f3a78ded925 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: 2022-09-21 +date: 2022-09-22 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 676f396e38cc2..deafbf79b1aed 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: 2022-09-21 +date: 2022-09-22 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 97372d71d53fb..7665687eeeb21 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 3d5eb7fe015f6..76385b837391c 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 3f990bbcb20d0..c56a3ae4b8e16 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 9e7dc24aae0e2..e68b7f3ca1944 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index c16ee014aeeb8..ea9fad5915ad3 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 619cdab0943f7..c63719ad85397 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 4850104f76366..b5bc104a0ce60 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 57b107d6beadb..5fcfd0203431c 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 90a09544d07e7..9cc6b1a542f2f 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: 2022-09-21 +date: 2022-09-22 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 e430f45966c72..0a43ee6b4356d 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: 2022-09-21 +date: 2022-09-22 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 167becc10bb92..7e9bbc61325bd 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: 2022-09-21 +date: 2022-09-22 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 5046f23612945..a2551bb06b839 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: 2022-09-21 +date: 2022-09-22 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 b7f3142a5db26..d832b2f6ba19a 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: 2022-09-21 +date: 2022-09-22 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 80dc9cea35aa6..448efa7620ec5 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 9720b4db69b1f..4fae8b05f4e6a 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -8860,6 +8860,112 @@ } ] }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics", + "type": "Object", + "tags": [], + "label": "[enableServiceMetrics]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "observability", "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting", @@ -10117,6 +10223,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.enableServiceMetrics", + "type": "string", + "tags": [], + "label": "enableServiceMetrics", + "description": [], + "signature": [ + "\"observability:apmEnableServiceMetrics\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.maxSuggestions", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index e4a65ba5f33e7..636feb22aad94 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 519 | 2 | 515 | 30 | +| 528 | 2 | 524 | 30 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index f87b8903f184c..55d8f562dfe26 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: 2022-09-21 +date: 2022-09-22 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 9bc3e41f6b9e1..baf9b97b47850 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 31334 | 180 | 21000 | 980 | +| 31370 | 180 | 21022 | 981 | ## Plugin Directory @@ -80,7 +80,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | 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/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 6 | 2 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 15 | 2 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 986 | 3 | 886 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -115,7 +115,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 519 | 2 | 515 | 30 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 528 | 2 | 524 | 30 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 21 | 0 | 21 | 3 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 243 | 2 | 187 | 12 | @@ -156,7 +156,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 132 | 0 | 91 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 61 | 0 | 59 | 2 | -| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 122 | 2 | 96 | 16 | +| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 125 | 2 | 99 | 17 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | urlDrilldown | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 12 | 0 | 12 | 0 | @@ -175,7 +175,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | 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. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 630 | 12 | 601 | 14 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 631 | 12 | 602 | 14 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 5c1ba3fbe59be..b870cf5f14637 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: 2022-09-21 +date: 2022-09-22 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 aa3a5a11aa480..8fb8adca44d0f 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: 2022-09-21 +date: 2022-09-22 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 c20d0362bae48..ed97b5d7991c2 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: 2022-09-21 +date: 2022-09-22 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 b58df3eacde86..932bb602229a1 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 8f47f3dda1591..7df9a27986349 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: 2022-09-21 +date: 2022-09-22 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 1e009c82de870..8a0dbdd763edc 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: 2022-09-21 +date: 2022-09-22 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 bf84cc9fa0b90..21f55bfde44f8 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: 2022-09-21 +date: 2022-09-22 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 8fb8ea0d3c2b7..0d3a66da043f4 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: 2022-09-21 +date: 2022-09-22 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 c9b068a72abff..35098620fec44 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: 2022-09-21 +date: 2022-09-22 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 a93096c35f284..8f802bca47dd3 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: 2022-09-21 +date: 2022-09-22 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 5c0f411010392..2ee5a06e9512f 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: 2022-09-21 +date: 2022-09-22 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 9eccbd9ed489d..d690ce3f16ec2 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: 2022-09-21 +date: 2022-09-22 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 6f35997066953..613210385f877 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: 2022-09-21 +date: 2022-09-22 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 e66c9d7da18f5..185e146c2db95 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: 2022-09-21 +date: 2022-09-22 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 a09ddaab21afb..51927a493f916 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: 2022-09-21 +date: 2022-09-22 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 fedcffb1b25d5..415b526e0b26c 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 60d6c667f7c91..15e420b2b0182 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 408b96cf182b1..b4ef6d48c46bf 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: 2022-09-21 +date: 2022-09-22 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 e0a7355048af7..b8e64fb777735 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: 2022-09-21 +date: 2022-09-22 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 e4f72340d087e..594c05507252c 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: 2022-09-21 +date: 2022-09-22 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 98cb1380a3503..07430e37fe600 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.devdocs.json b/api_docs/stack_alerts.devdocs.json index 209dcb93ef3a1..3850659e8a4d7 100644 --- a/api_docs/stack_alerts.devdocs.json +++ b/api_docs/stack_alerts.devdocs.json @@ -63,7 +63,7 @@ "signature": [ "\"stackAlerts\"" ], - "path": "x-pack/plugins/stack_alerts/common/index.ts", + "path": "x-pack/plugins/stack_alerts/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 0d23909034ae8..90bbd271763c1 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: 2022-09-21 +date: 2022-09-22 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 8b250938595f7..ed1fa872c8a04 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: 2022-09-21 +date: 2022-09-22 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 d33ad32bec354..b3cb07fff0933 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: 2022-09-21 +date: 2022-09-22 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 d170306872998..b23d2d1888173 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: 2022-09-21 +date: 2022-09-22 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 6f9d668fb6cea..226591c004f88 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: 2022-09-21 +date: 2022-09-22 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 ed6b7e61717c5..08f903b0515f4 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: 2022-09-21 +date: 2022-09-22 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 7ad0996faeb49..75afad06122f6 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 341dca64a770b..c0d37bafaf0f7 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index ef634330a3c39..12818ccc00767 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index d4370cbd83025..2c93efb7ba04b 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: 2022-09-21 +date: 2022-09-22 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 6c8003d4f5dfd..9959fc8d580d4 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: 2022-09-21 +date: 2022-09-22 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 146259f72f7bb..215ab9ab7a5a3 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: 2022-09-21 +date: 2022-09-22 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 427db15b1b3ad..bb4305309d371 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: 2022-09-21 +date: 2022-09-22 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 4d489503da198..117b0750b9d78 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 4d5026322ffbb..89093ced7129d 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -366,7 +366,7 @@ }, " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; onTextBasedSavedAndExit?: (({ onSave }: ", "OnSaveTextLanguageQueryProps", - ") => void) | undefined; showSubmitButton?: boolean | undefined; suggestionsSize?: ", + ") => void) | undefined; showSubmitButton?: boolean | undefined; submitButtonStyle?: \"auto\" | \"full\" | \"iconOnly\" | undefined; suggestionsSize?: ", "SuggestionsListSize", " | undefined; isScreenshotMode?: boolean | undefined; onFiltersUpdated?: ((filters: ", "Filter", @@ -1030,6 +1030,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.docLinks", + "type": "Object", + "tags": [], + "label": "docLinks", + "description": [], + "signature": [ + "DocLinksStart" + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.IUnifiedSearchPluginServices.data", @@ -1470,6 +1484,31 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.QueryStringInputProps.appName", + "type": "string", + "tags": [], + "label": "appName", + "description": [], + "path": "src/plugins/unified_search/public/query_string_input/query_string_input.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.QueryStringInputProps.deps", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "QueryStringInputDependencies" + ], + "path": "src/plugins/unified_search/public/query_string_input/query_string_input.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.QueryStringInputProps.nonKqlMode", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 67b3d84096fc4..69b4ac2504553 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-servic | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 122 | 2 | 96 | 16 | +| 125 | 2 | 99 | 17 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 9d8991e0ea45d..929d32a836b61 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-servic | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 122 | 2 | 96 | 16 | +| 125 | 2 | 99 | 17 | ## Client diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 46e7ae60e2614..ccf74575df120 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: 2022-09-21 +date: 2022-09-22 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 a6dfb697d8c5a..a736d4d52eba8 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: 2022-09-21 +date: 2022-09-22 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 c281ebdbd88df..03f35c432a9f8 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: 2022-09-21 +date: 2022-09-22 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 800fbabeaf203..7f07f304187fc 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: 2022-09-21 +date: 2022-09-22 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 f574152d0a191..ba53a8580fdff 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: 2022-09-21 +date: 2022-09-22 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 625d6628b13cf..46fbb7e99329f 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: 2022-09-21 +date: 2022-09-22 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 6bd851dad3a56..680635f94e279 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: 2022-09-21 +date: 2022-09-22 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 8c8bad65b3868..eae6da767fea5 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: 2022-09-21 +date: 2022-09-22 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 9037c2163f3d5..7fdec24dd1ac8 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: 2022-09-21 +date: 2022-09-22 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 69e98952c0f79..1de3ded2cdc39 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: 2022-09-21 +date: 2022-09-22 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 7f049ea5d969d..4c4ad09221dec 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: 2022-09-21 +date: 2022-09-22 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 83c0958ab36ce..ca416be51f23c 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: 2022-09-21 +date: 2022-09-22 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 51b71c88e6a10..b8bc8c502fb16 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index f4ce7d07d4c8f..f9d7be383b81f 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -2546,6 +2546,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.EditorRenderProps.unifiedSearch", + "type": "Object", + "tags": [], + "label": "unifiedSearch", + "description": [], + "signature": [ + { + "pluginId": "unifiedSearch", + "scope": "public", + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.UnifiedSearchPublicPluginStart", + "text": "UnifiedSearchPublicPluginStart" + } + ], + "path": "src/plugins/visualizations/public/visualize_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "visualizations", "id": "def-public.EditorRenderProps.linked", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 90b1267c266a9..4fd3e74a04cf9 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: 2022-09-21 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 630 | 12 | 601 | 14 | +| 631 | 12 | 602 | 14 | ## Client diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc index e477d7237c273..b5c59bb86bc70 100644 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -437,6 +437,60 @@ the security incident. The IPs are added as observables to the security incident `message`:: (Required, string) The message to log. ===== + +.{swimlane} connectors +[%collapsible%open] +===== +`subAction`:: +(Required, string) The action to test. It must be `pushToService`. + +`subActionParams`:: +(Required, object) The set of configuration properties. ++ +.Properties of `subActionParams` +[%collapsible%open] +====== +`comments`::: +(Optional, array of objects) Additional information that is sent to {swimlane}. ++ +.Properties of `comments` objects +[%collapsible%open] +======= +comment:::: +(string) A comment related to the incident. For example, describe how to +troubleshoot the issue. + +commentId:::: +(integer) A unique identifier for the comment. + +======= + +`incident`::: +(Required, object) Information necessary to create or update a {swimlane} incident. ++ +.Properties of `incident` +[%collapsible%open] +======= +`alertId`:::: +(Optional, string) The alert identifier. + +`caseId`:::: +(Optional, string) The case identifier for the incident. + +`caseName`:::: +(Optional, string) The case name for the incident. + +`description`:::: +(Optional, string) The description of the incident. + +`ruleName`:::: +(Optional, string) The rule name. + +`severity`:::: +(Optional, string) The severity of the incident. +======= +====== +===== ==== -- @@ -549,6 +603,41 @@ The API returns the following: } -------------------------------------------------- +Create then update a {swimlane} incident: +[source,sh] +-------------------------------------------------- +POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute +{ + "params":{ + "subAction":"pushToService", + "subActionParams":{ + "incident":{ + "description":"Description of the incident", + "caseName":"Case name", + "caseId":"1000" + }, + "comments":[ + {"commentId":"1","comment":"A comment about the incident"} + ] + } + } +} + +POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute +{ + "params":{ + "subAction":"pushToService", + "subActionParams":{ + "incident":{ + "caseId":"1000", + "caseName":"A new case name" + } + } + } +} +-------------------------------------------------- +// KIBANA + Retrieve the list of choices for a {sn-itom} connector: [source,sh] @@ -583,4 +672,5 @@ The API returns the severity and urgency choices, for example: {"dependent_value":"","label":"3 - Low","value":"3","element":"urgency"}], "connector_id":"9d9be270-2fd2-11ed-b0e0-87533c532698" } --------------------------------------------------- \ No newline at end of file +-------------------------------------------------- + diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index e4524b7fa7828..e84cc4c4ee0b0 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -177,7 +177,7 @@ for use in their own application. |{kib-repo}blob/{branch}/src/plugins/guided_onboarding/README.md[guidedOnboarding] -|A Kibana plugin +|This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. |{kib-repo}blob/{branch}/src/plugins/home/README.md[home] diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index 32811cfbe7728..9547dbaf4477f 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -3,23 +3,11 @@ *Discover* displays your documents in table format, so you can -best explore your data. -Use the document table to resize columns, set row height, +best explore your data. Resize columns, set row height, perform multi-column sorting, compare data, and more. -++++ - - -
-++++ +[role="screenshot"] +image:images/customer.png[Customer last name, first initial in the document table] [float] [[document-explorer-columns]] diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index ee1247501e8da..7fccd6c6c93f7 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -1,11 +1,10 @@ [[managing-saved-objects]] -== Saved Objects +== Manage saved objects -The *Saved Objects* UI helps you keep track of and manage your saved objects. These objects -store data for later use, including dashboards, visualizations, maps, data views, -Canvas workpads, and more. +Edit, import, export, and copy your saved objects. These objects include +dashboards, visualizations, maps, {data-sources}, *Canvas* workpads, and other saved objects. -To get started, open the main menu, then click *Stack Management > Saved Objects*. +To get started, open the main menu, and then click *Stack Management > Saved Objects*. [role="screenshot"] image::images/management-saved-objects.png[Saved Objects] @@ -13,23 +12,24 @@ image::images/management-saved-objects.png[Saved Objects] [float] === Required permissions -The `Saved Objects Management` {kib} privilege is required to access the *Saved Objects* UI. +To access *Saved Objects*, you must have the required `Saved Objects Management` {kib} privilege. -To add the privilege, open the menu, then click *Stack Management > Roles*. +To add the privilege, open the main menu, and then click *Stack Management > Roles*. -NOTE: -Granting access to Saved Objects Management will authorize users to manage all saved objects in {kib}, including objects that are managed by applications they may not otherwise be authorized to access. +NOTE: Granting access to `Saved Objects Management` authorizes users to +manage all saved objects in {kib}, including objects that are managed by +applications they may not otherwise be authorized to access. [float] [[managing-saved-objects-view]] === View and delete -* To view and edit an object in its associated application, click the object title. +* To view and edit a saved object in its associated application, click the object title. * To show objects that use this object, so you know the impact of deleting it, click the actions icon image:images/actions_icon.png[Actions icon] -and select *Relationships*. +and then select *Relationships*. * To delete one or more objects, select their checkboxes, and then click *Delete*. @@ -37,58 +37,67 @@ and select *Relationships*. [[managing-saved-objects-export-objects]] === Import and export -Using the import and export actions, you can move objects between different -{kib} instances. This action is useful when you -have multiple environments for development and production. -Import and export also work well when you have a large number -of objects to update and want to batch the process. +Use import and export to move objects between different {kib} instances. +These actions are useful when you have multiple environments for development and production. +Import and export also work well when you have a large number of objects to update and want to batch the process. -In addition to the user interface, {kib} provides beta <> and <> APIs if -you want to automate this process. +{kib} also provides <> and +<> APIs to automate this process. -[float] -==== Compatibility across versions - -With each release, {kib} introduces changes to the way saved objects are stored. When importing a saved object, {kib} will run the necessary migrations to ensure that the imported saved objects are compatible with the current version. - -However, saved objects can only be imported into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of {kib}. See the table below for compatibility examples: - -|======= -| Exporting version | Importing version | Compatible? -| 6.7.0 | 6.8.1 | Yes -| 6.8.1 | 7.3.0 | Yes -| 7.3.0 | 7.11.1 | Yes -| 7.11.1 | 7.6.0 | No -| 6.8.1 | 8.0.0 | No -|======= [float] ==== Import -You can import multiple objects in a single operation. Click *Import* and -navigate to the NDJSON file that -represents the objects to import. By default, +Import multiple objects in a single operation. + +. In the toolbar, click *Import*. +. Select the NDJSON file that +includes the objects you want to import. +. Select the import options. By default, saved objects already in {kib} are overwritten. +. Click *Import*. NOTE: The <> configuration setting -limits the number of saved objects which may be included in this file. Similarly, the +limits the number of saved objects to include in the file. The <> setting limits the overall -size of the file that can be imported. +size of the file that you can import. [float] ==== Export -You have two options for exporting saved objects. +Export objects by selection or type. -* Select the checkboxes of objects that you want to export, and then click *Export*. -* Click *Export x objects*, and export objects by type. +* To export specific objects, select them in the table, and then click *Export*. +* To export objects by type, click *Export objects* in the toolbar. -This action creates an NDJSON with all your saved objects. By default, the NDJSON includes child objects that are related to the saved -objects. Exported dashboards include their associated data views. +{kib} creates an NDJSON with all your saved objects. By default, the NDJSON includes child objects related to the saved +objects. Exported dashboards include their associated {data-sources}. NOTE: The <> configuration setting -limits the number of saved objects which may be exported. +limits the number of saved objects that you can export. + +[float] +==== Compatibility across versions + +With each release, {kib} introduces changes to the way saved objects are stored. +When importing a saved object, {kib} runs the necessary migrations to ensure +that the imported saved objects are compatible with the current version. + +However, saved objects can only be imported into the same version, +a newer minor on the same major, or the next major. +Exported saved objects are not backward compatible and cannot be imported +into an older version of {kib}. For example: + +|======= +| Exporting version | Importing version | Compatible? +| 6.7.0 | 6.8.1 | Yes +| 6.8.1 | 7.3.0 | Yes +| 7.3.0 | 7.11.1 | Yes +| 7.11.1 | 7.6.0 | No +| 6.8.1 | 8.0.0 | No +|======= + [float] @@ -96,12 +105,16 @@ limits the number of saved objects which may be exported. [[managing-saved-objects-copy-to-space]] === Copy to other {kib} spaces -To copy a saved object to another space, click the actions icon image:images/actions_icon.png[Actions icon] -and select *Copy to spaces*. From here, you can select the spaces in which to copy the object. -You can also select whether to automatically overwrite any conflicts in the target spaces, or -resolve them manually. +Copy saved objects and their related objects between spaces. -WARNING: The copy operation automatically includes child objects that are related to the saved objects. If you don't want this behavior, use +. Click the actions icon image:images/actions_icon.png[Actions icon]. +. Click *Copy to spaces*. +. Select the spaces in which to copy the object. +. Specify whether to automatically overwrite any objects that already exist +in the target spaces, or resolve them on a per-object basis. ++ +The copy operation automatically includes child objects that are related to +the saved object. If you don't want this behavior, use the <> instead. [float] @@ -109,13 +122,18 @@ the <> instead. [[managing-saved-objects-share-to-space]] === Share to other {kib} spaces -To share a saved object to another space -- which makes a single saved object available in multiple spaces -- click the actions icon -image:images/actions_icon.png[Actions icon] and select *Share to spaces*. From here, you can select the spaces in which to share the object, -or indicate that you want the object to be shared to _all spaces_, which includes those that exist now and any created in the future. +Make a single saved object available in multiple spaces. -Not all saved object types are shareable. If an object is shareable, the Spaces column shows which spaces it exists in. You can also click +. Click the actions icon +image:images/actions_icon.png[Actions icon]. +. Select *Share to spaces*. +. Select the spaces in which to share the object. +Or, indicate that you want the object to be shared to _all spaces_, +which includes those that exist now and any created in the future. ++ +Not all saved object types are shareable. If an object is shareable, the *Spaces* column shows where the object exists. You can click those space icons to open the Share UI. - -WARNING: The share operation automatically includes child objects that are related to the saved objects. ++ +The share operation automatically includes child objects that are related to the saved objects. include::saved-objects/saved-object-ids.asciidoc[] diff --git a/docs/management/managing-tags.asciidoc b/docs/management/managing-tags.asciidoc index a0b3dce7f4b27..b9fbe85760786 100644 --- a/docs/management/managing-tags.asciidoc +++ b/docs/management/managing-tags.asciidoc @@ -2,8 +2,10 @@ [[managing-tags]] == Tags -Tags enable you to categorize your saved objects. -You can then filter for related objects based on shared tags. +Use tags to categorize your saved objects, +then filter for related objects based on shared tags. + +To get started, open the main menu, and then click *Stack Management > Tags*. [role="screenshot"] image::images/tags/tag-management-section.png[Tags management] @@ -29,7 +31,6 @@ from the global search. Create a tag to assign to your saved objects. -. Open the main menu, and then click *Stack Management > Tags*. . Click *Create tag*. . Enter a name and select a color for the new tag. @@ -41,23 +42,21 @@ The name cannot be longer than 50 characters. [[settings-assign-tag]] === Assign a tag to an object -To assign and remove tags from saved objects, you must have `write` permission +To assign and remove tags, you must have `write` permission on the objects to which you assign the tags. -. In the *Tags* view, find the tag you want to assign. -. Click the action menu (...) in the tag row, -and then select the *Manage assignments* action. +. Find the tag you want to assign. +. Click the actions icon +image:images/actions_icon.png[Actions icon], +and then select *Manage assignments*. . Select the objects to which you want to assign or remove tags. + [role="screenshot"] -image::images/tags/manage-assignments-flyout.png[Assign flyout] +image::images/tags/manage-assignments-flyout.png[Assign flyout, width=75%] . Click *Save tag assignments*. -TIP: To assign, delete, or clear multiple tags at once, -select their checkboxes in the *Tags* view, and then select -the desired action from the *selected tags* menu. [float] [[settings-delete-tag]] @@ -65,6 +64,11 @@ the desired action from the *selected tags* menu. When you delete a tag, you remove it from all saved objects that use it. -. Click the action menu (...) in the tag row, and then select the *Delete* action. +. Click the actions icon +image:images/actions_icon.png[Actions icon], and then select *Delete*. . Click *Delete tag*. + +TIP: To assign, delete, or clear multiple tags, +select them in the *Tags* view, and then select +the action from the *selected tags* menu. diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index fba7a32e17f1b..de5e8c686c61b 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -77,9 +77,6 @@ Maximum number of child items displayed when viewing trace details. Defaults to `xpack.observability.annotations.index` {ess-icon}:: Index name where Observability annotations are stored. Defaults to `observability-annotations`. -`xpack.apm.searchAggregatedServiceMetrics` {ess-icon}:: - Enables Service metrics. Defaults to `false`. When set to `true`, additional configuration in APM Server is required. - `xpack.apm.searchAggregatedTransactions` {ess-icon}:: Enables Transaction histogram metrics. Defaults to `auto` so the UI will use metric indices over transaction indices for transactions if aggregated transactions are found. When set to `always`, additional configuration in APM Server is required. When set to `never` and aggregated transactions are not used. + diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 5e87efc5e8aca..03274bec76714 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -95,7 +95,7 @@ a field, select *Edit Settings*, and change *Terms per hop*. Documents that match a blocked term are not allowed in the graph. To block a term, select its vertex and click the block icon -image:user/graph/images/graph-block-button.png[Block selection] +image:user/graph/images/graph-block-button.png[Block list] in the control panel. For a list of blocked terms, go to *Settings > Blocked terms*. diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 640a824180480..399de14d5f18c 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -1,8 +1,12 @@ [[whats-new]] -== What's new in 8.0 +== What's new in {minor-version} -This section summarizes the most important changes in each release. For the -full list, see <> and <>. +Here are the highlights of what's new and improved in {minor-version}. +For detailed information about this release, +check the <>. + +Previous versions: {kibana-ref-all}/8.4/whats-new.html[8.4] | {kibana-ref-all}/8.3/whats-new.html[8.3] | {kibana-ref-all}/8.2/whats-new.html[8.2] +| {kibana-ref-all}/8.1/whats-new.html[8.1] | {kibana-ref-all}/8.0/whats-new.html[8.0] //NOTE: The notable-highlights tagged regions are re-used in the //Installation and Upgrade Guide diff --git a/examples/guided_onboarding_example/README.md b/examples/guided_onboarding_example/README.md index c00f5c7a4656c..544db5b5731e8 100755 --- a/examples/guided_onboarding_example/README.md +++ b/examples/guided_onboarding_example/README.md @@ -1,6 +1,6 @@ # guidedOnboardingExample -A Kibana plugin +This plugin contains code examples for the Guided Onboarding plugin. More information can be found in `KIBANA_FOLDER/src/plugins/guided_onboarding/README.md` --- diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index 8c372bd066271..157b13f1276c0 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -10,9 +10,10 @@ import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { CoreStart } from '@kbn/core/public'; import { EuiButton, - EuiFieldNumber, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -29,7 +30,6 @@ import { GuidedOnboardingState, UseCase, } from '@kbn/guided-onboarding-plugin/public'; -import { CoreStart } from '@kbn/core/public'; interface MainProps { guidedOnboarding: GuidedOnboardingPluginStart; @@ -51,9 +51,11 @@ export const Main = (props: MainProps) => { ); useEffect(() => { - const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => { - setGuideState(newState); - }); + const subscription = guidedOnboardingApi + ?.fetchGuideState$() + .subscribe((newState: GuidedOnboardingState) => { + setGuideState(newState); + }); return () => subscription?.unsubscribe(); }, [guidedOnboardingApi]); @@ -208,7 +210,7 @@ export const Main = (props: MainProps) => { - setSelectedStep(e.target.value)} /> diff --git a/examples/guided_onboarding_example/public/components/step_one.tsx b/examples/guided_onboarding_example/public/components/step_one.tsx index 65b4d8f1f4ad9..bacb43ad0f67a 100644 --- a/examples/guided_onboarding_example/public/components/step_one.tsx +++ b/examples/guided_onboarding_example/public/components/step_one.tsx @@ -18,6 +18,8 @@ import { EuiSpacer, } from '@elastic/eui'; +import useObservable from 'react-use/lib/useObservable'; + import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; interface GuidedOnboardingExampleAppDeps { @@ -28,17 +30,14 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => const { guidedOnboardingApi } = guidedOnboarding; const [isTourStepOpen, setIsTourStepOpen] = useState(false); - useEffect(() => { - const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => { - const { activeGuide: guide, activeStep: step } = newState; - - if (guide === 'search' && step === 'add_data') { - setIsTourStepOpen(true); - } - }); - return () => subscription?.unsubscribe(); - }, [guidedOnboardingApi]); + const isTourActive = useObservable( + guidedOnboardingApi!.isGuideStepActive$('search', 'add_data'), + false + ); + useEffect(() => { + setIsTourStepOpen(isTourActive); + }, [isTourActive]); return ( <> @@ -79,10 +78,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => > { - await guidedOnboardingApi?.updateGuideState({ - activeGuide: 'search', - activeStep: 'search_experience', - }); + await guidedOnboardingApi?.completeGuideStep('search', 'add_data'); }} > Complete step 1 diff --git a/examples/guided_onboarding_example/public/components/step_two.tsx b/examples/guided_onboarding_example/public/components/step_two.tsx index d5962dc5e1cd9..9f96532450bfc 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -80,10 +80,7 @@ export const StepTwo = (props: StepTwoProps) => { > { - await guidedOnboardingApi?.updateGuideState({ - activeGuide: 'search', - activeStep: 'optimize', - }); + await guidedOnboardingApi?.completeGuideStep('search', 'browse_docs'); }} > Complete step 2 diff --git a/package.json b/package.json index c138d74d8f0a8..c257e1a46fc9a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dashboarding" ], "private": true, - "version": "8.5.0", + "version": "8.6.0", "branch": "main", "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", @@ -109,7 +109,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.3.0-canary.1", "@elastic/ems-client": "8.3.3", - "@elastic/eui": "64.0.4", + "@elastic/eui": "64.0.5", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 051d0ac9bf27f..56ef73801d5a9 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -281,6 +281,7 @@ filegroup( "//packages/kbn-utils:build", "//packages/kbn-yarn-lock-validator:build", "//packages/shared-ux/avatar/solution:build", + "//packages/shared-ux/avatar/user_profile/impl:build", "//packages/shared-ux/button_toolbar:build", "//packages/shared-ux/button/exit_full_screen/impl:build", "//packages/shared-ux/button/exit_full_screen/mocks:build", @@ -310,6 +311,9 @@ filegroup( "//packages/shared-ux/prompt/no_data_views/impl:build", "//packages/shared-ux/prompt/no_data_views/mocks:build", "//packages/shared-ux/prompt/no_data_views/types:build", + "//packages/shared-ux/router/impl:build", + "//packages/shared-ux/router/mocks:build", + "//packages/shared-ux/router/types:build", "//packages/shared-ux/storybook/config:build", "//packages/shared-ux/storybook/mock:build", "//x-pack/packages/ml/agg_utils:build", @@ -581,6 +585,7 @@ filegroup( "//packages/kbn-utils:build_types", "//packages/kbn-yarn-lock-validator:build_types", "//packages/shared-ux/avatar/solution:build_types", + "//packages/shared-ux/avatar/user_profile/impl:build_types", "//packages/shared-ux/button_toolbar:build_types", "//packages/shared-ux/button/exit_full_screen/impl:build_types", "//packages/shared-ux/button/exit_full_screen/mocks:build_types", @@ -601,6 +606,8 @@ filegroup( "//packages/shared-ux/page/solution_nav:build_types", "//packages/shared-ux/prompt/no_data_views/impl:build_types", "//packages/shared-ux/prompt/no_data_views/mocks:build_types", + "//packages/shared-ux/router/impl:build_types", + "//packages/shared-ux/router/mocks:build_types", "//packages/shared-ux/storybook/config:build_types", "//packages/shared-ux/storybook/mock:build_types", "//x-pack/packages/ml/agg_utils:build_types", diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts index 2609859ae9d8c..6e21336bbb6fe 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts @@ -22,16 +22,26 @@ describe('AnalyticsService', () => { expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(1); await expect( await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[0][0].context$) - ).toMatchInlineSnapshot(` + ).toMatchInlineSnapshot( + { + branch: expect.any(String), + buildNum: 9007199254740991, + buildSha: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + isDev: true, + isDistributable: false, + version: expect.any(String), + }, + ` Object { - "branch": "main", + "branch": Any, "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "isDev": true, "isDistributable": false, - "version": "8.5.0", + "version": Any, } - `); + ` + ); }); test('should register the `performance_metric` event type on creation', () => { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts index b8b218db3d8fd..811d9d95831ef 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts @@ -51,7 +51,7 @@ describe('AgentManager', () => { expect(HttpAgent).toBeCalledTimes(1); expect(HttpAgent).toBeCalledWith({ keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxFreeSockets: 256, maxSockets: 256, scheduling: 'lifo', @@ -68,7 +68,7 @@ describe('AgentManager', () => { expect(HttpAgent).toBeCalledTimes(1); expect(HttpAgent).toBeCalledWith({ keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxFreeSockets: 32, maxSockets: 1024, scheduling: 'fifo', diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts index 237f6964d975b..eb68014561d77 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts @@ -13,7 +13,7 @@ import { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; const HTTPS = 'https:'; const DEFAULT_CONFIG: HttpAgentOptions = { keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxSockets: 256, maxFreeSockets: 256, scheduling: 'lifo', diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts index 7c14da7e4b421..7a6d94c31f291 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts @@ -65,9 +65,14 @@ const existsSchema = s.object({ // For more details see how the types are defined in the elasticsearch javascript client: // https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295 const boolSchema = s.object({ - bool: s.object({ - must_not: s.oneOf([termSchema]), - }), + bool: s.oneOf([ + s.object({ + must_not: s.oneOf([termSchema, existsSchema]), + }), + s.object({ + filter: s.oneOf([termSchema, existsSchema]), + }), + ]), }); const orderSchema = s.oneOf([ diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts index 1772b5f655713..26a908bd085ab 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts @@ -105,15 +105,16 @@ export function getBreakdownMetrics(events: ApmFields[]) { lastMeasurement = timestamp; } + const instance = pickBy(event, instancePicker); + const key = { '@timestamp': event['@timestamp']! - (event['@timestamp']! % (30 * 1000)), 'transaction.type': transaction['transaction.type'], 'transaction.name': transaction['transaction.name'], ...pickBy(event, metricsetPicker), + ...instance, }; - const instance = pickBy(event, instancePicker); - const metricsetId = objectHash(key); let metricset = metricsets.get(metricsetId); @@ -121,7 +122,6 @@ export function getBreakdownMetrics(events: ApmFields[]) { if (!metricset) { metricset = { ...key, - ...instance, 'processor.event': 'metric', 'processor.name': 'metric', 'metricset.name': `span_breakdown`, diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 413e8905bea93..8c39a15033ce1 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -447,6 +447,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { monitorUptimeSynthetics: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-uptime-synthetics.html`, userExperience: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/user-experience.html`, createAlerts: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/create-alerts.html`, + syntheticsCommandReference: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-configuration.html#synthetics-configuration-playwright-options`, }, alerting: { guide: `${KIBANA_DOCS}create-and-manage-rules.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 1ee0d5414b275..3735c1abbddbf 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -331,6 +331,7 @@ export interface DocLinks { monitorUptimeSynthetics: string; userExperience: string; createAlerts: string; + syntheticsCommandReference: string; }>; readonly alerting: Record; readonly maps: Readonly<{ diff --git a/packages/kbn-es-query/src/filters/helpers/meta_filter.ts b/packages/kbn-es-query/src/filters/helpers/meta_filter.ts index 484b85d608cff..3406ad5a5a1ce 100644 --- a/packages/kbn-es-query/src/filters/helpers/meta_filter.ts +++ b/packages/kbn-es-query/src/filters/helpers/meta_filter.ts @@ -113,11 +113,7 @@ export const unpinFilter = (filter: Filter) => * @public */ export const isFilter = (x: unknown): x is Filter => - !!x && - typeof x === 'object' && - !!(x as Filter).meta && - typeof (x as Filter).meta === 'object' && - typeof (x as Filter).meta.disabled === 'boolean'; + !!x && typeof x === 'object' && !!(x as Filter).meta && typeof (x as Filter).meta === 'object'; /** * @param {unknown} filters diff --git a/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc b/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc new file mode 100644 index 0000000000000..1fab1b9cb7d84 --- /dev/null +++ b/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-avatar-user-profile-components", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/router/impl/kibana.jsonc b/packages/shared-ux/router/impl/kibana.jsonc new file mode 100644 index 0000000000000..77f3eca900702 --- /dev/null +++ b/packages/shared-ux/router/impl/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/router/mocks/kibana.jsonc b/packages/shared-ux/router/mocks/kibana.jsonc new file mode 100644 index 0000000000000..8f3aef23a2081 --- /dev/null +++ b/packages/shared-ux/router/mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router-mocks", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/router/types/kibana.jsonc b/packages/shared-ux/router/types/kibana.jsonc new file mode 100644 index 0000000000000..4e328b93d6081 --- /dev/null +++ b/packages/shared-ux/router/types/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router-types", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 840d7564681a8..de299c1298f9f 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -84,6 +84,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.3.3': ['Elastic License 2.0'], - '@elastic/eui@64.0.4': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@64.0.5': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts index 298b93c3a2fdb..c9e954a081ca2 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts @@ -10,15 +10,15 @@ import _ from 'lodash'; import type { KibanaExecutionContext } from '@kbn/core/public'; import type { ControlGroupInput } from '@kbn/controls-plugin/public'; +import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; import { compareFilters, - isFilterPinned, - migrateFilter, COMPARE_ALL_OPTIONS, - type Filter, + Filter, + isFilterPinned, + TimeRange, } from '@kbn/es-query'; -import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; -import type { TimeRange } from '@kbn/es-query'; +import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; import type { DashboardSavedObject } from '../../saved_dashboards'; import { getTagsFromSavedDashboard, migrateAppState } from '.'; @@ -72,6 +72,7 @@ export const savedObjectToDashboardState = ({ if (rawState.timeRestore) { rawState.timeRange = { from: savedDashboard.timeFrom, to: savedDashboard.timeTo } as TimeRange; } + rawState.controlGroupInput = deserializeControlGroupFromDashboardSavedObject( savedDashboard ) as ControlGroupInput; @@ -89,9 +90,10 @@ export const stateToDashboardContainerInput = ({ executionContext, }: StateToDashboardContainerInputProps): DashboardContainerInput => { const { - data: { query: queryService }, + data: { + query: { filterManager, timefilter: timefilterService }, + }, } = pluginServices.getServices(); - const { filterManager, timefilter: timefilterService } = queryService; const { timefilter } = timefilterService; const { @@ -109,6 +111,7 @@ export const stateToDashboardContainerInput = ({ filters: dashboardFilters, } = dashboardState; + const migratedDashboardFilters = mapAndFlattenFilters(_.cloneDeep(dashboardFilters)); return { refreshConfig: timefilter.getRefreshInterval(), filters: filterManager @@ -116,8 +119,8 @@ export const stateToDashboardContainerInput = ({ .filter( (filter) => isFilterPinned(filter) || - dashboardFilters.some((dashboardFilter) => - filtersAreEqual(migrateFilter(_.cloneDeep(dashboardFilter)), filter) + migratedDashboardFilters.some((dashboardFilter) => + filtersAreEqual(dashboardFilter, filter) ) ), isFullScreenMode: fullScreenMode, diff --git a/src/plugins/data_views/common/data_views/data_view.ts b/src/plugins/data_views/common/data_views/data_view.ts index 42ab6d10834d4..f7f6451ca0453 100644 --- a/src/plugins/data_views/common/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -15,7 +15,7 @@ import type { } from '@kbn/field-formats-plugin/common'; import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { CharacterNotAllowedInField } from '@kbn/kibana-utils-plugin/common'; -import _, { cloneDeep, each, reject } from 'lodash'; +import { cloneDeep, each, reject } from 'lodash'; import type { DataViewAttributes, FieldAttrs, FieldAttrSet } from '..'; import type { DataViewField, IIndexPatternFieldList } from '../fields'; import { fieldList } from '../fields'; @@ -407,9 +407,6 @@ export class DataView implements DataViewBase { * Returns index pattern as saved object body for saving */ getAsSavedObjectBody(): DataViewAttributes { - const fieldFormatMap = _.isEmpty(this.fieldFormatMap) - ? undefined - : JSON.stringify(this.fieldFormatMap); const fieldAttrs = this.getFieldAttrs(); const runtimeFieldMap = this.runtimeFieldMap; @@ -419,7 +416,7 @@ export class DataView implements DataViewBase { timeFieldName: this.timeFieldName, sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, fields: JSON.stringify(this.fields?.filter((field) => field.scripted) ?? []), - fieldFormatMap, + fieldFormatMap: this.fieldFormatMap ? JSON.stringify(this.fieldFormatMap) : undefined, type: this.type!, typeMeta: JSON.stringify(this.typeMeta ?? {}), allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, diff --git a/src/plugins/data_views/common/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts index af95942393e11..a096cbe07cd53 100644 --- a/src/plugins/data_views/common/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -350,6 +350,26 @@ describe('IndexPatterns', () => { expect(async () => await indexPatterns.get(id)).toBeDefined(); }); + test('can set and remove field format', async () => { + const id = 'id'; + setDocsourcePayload(id, savedObject); + const dataView = await indexPatterns.get(id); + dataView.setFieldFormat('field', { id: 'formatId' }); + await indexPatterns.updateSavedObject(dataView); + let lastCall = (savedObjectsClient.update as jest.Mock).mock.calls.pop() ?? []; + let [, , attrs] = lastCall; + expect(attrs).toHaveProperty('fieldFormatMap'); + expect(attrs.fieldFormatMap).toMatchInlineSnapshot(`"{\\"field\\":{\\"id\\":\\"formatId\\"}}"`); + dataView.deleteFieldFormat('field'); + await indexPatterns.updateSavedObject(dataView); + lastCall = (savedObjectsClient.update as jest.Mock).mock.calls.pop() ?? []; + [, , attrs] = lastCall; + + // https://github.com/elastic/kibana/issues/134873: must keep an empty object and not delete it + expect(attrs).toHaveProperty('fieldFormatMap'); + expect(attrs.fieldFormatMap).toMatchInlineSnapshot(`"{}"`); + }); + describe('getDefaultDataView', () => { beforeEach(() => { indexPatterns.clearCache(); diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index cf0bf824c6b1a..b2a42c351a102 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -18,7 +18,8 @@ "dataViewFieldEditor", "dataViewEditor", "expressions", - "unifiedFieldList" + "unifiedFieldList", + "unifiedSearch" ], "optionalPlugins": [ "home", diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 5e4140ecb6d48..86675ae54441a 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -45,6 +45,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { DiscoverAppLocator } from './locator'; import { getHistory } from './kibana_services'; import { DiscoverStartPlugins } from './plugin'; @@ -88,6 +89,7 @@ export interface DiscoverServices { charts: ChartsPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; savedObjectsTagging?: SavedObjectsTaggingApi; + unifiedSearch: UnifiedSearchPublicPluginStart; } export const buildServices = memoize(function ( @@ -136,5 +138,6 @@ export const buildServices = memoize(function ( charts: plugins.charts, savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), savedObjectsManagement: plugins.savedObjectsManagement, + unifiedSearch: plugins.unifiedSearch, }; }); diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index e02e6519bfda3..196fb15fb11d8 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -38,6 +38,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { PLUGIN_ID } from '../common'; import { DocViewInput, DocViewInputFn } from './services/doc_views/doc_views_types'; import { DocViewsRegistry } from './services/doc_views/doc_views_registry'; @@ -180,6 +181,7 @@ export interface DiscoverStartPlugins { expressions: ExpressionsStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } /** diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index c0f16b034d043..1daa04d223e2b 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -1,9 +1,77 @@ -# guidedOnboarding +# Guided Onboarding -A Kibana plugin +This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. + +The guided onboarding plugin includes a client-side code for the UI and the server side code for the internal API. The server-side code is not intended for external use. + +The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins. --- ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. +1. To enable the UI, add `guidedOnboarding.ui: true` to the file `KIBANA_FOLDER/config/kibana.dev.yml`. + +2. Start Kibana with examples `yarn start --run-examples` to be able to see the guidedOnboardingExample plugin. + +3. Navigate to `/app/guidedOnboardingExample` to start a guide and check the button in the header. + +## API service +*Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples.* + +The guided onboarding plugin exposes an API service from its start contract that is intended to be used by other plugins. The API service allows consumers to access the current state of the guided onboarding process and manipulate it. + +To use the API service in your plugin, declare the guided onboarding plugin as a dependency in the file `kibana.json` of your plugin. Add the API service to your plugin's start dependencies to rely on the provided TypeScript interface: +``` +export interface AppPluginStartDependencies { + guidedOnboarding: GuidedOnboardingPluginStart; +} +``` +The API service is now available to your plugin in the setup lifecycle function of your plugin +``` +// startDependencies is of type AppPluginStartDependencies +const [coreStart, startDependencies] = await core.getStartServices(); +``` +or in the start lifecycle function of your plugin. +``` +public start(core: CoreStart, startDependencies: AppPluginStartDependencies) { + ... +} +``` + +### isGuideStepActive$(guideID: string, stepID: string): Observable\ +*Also see `KIBANA_FOLDER/examples/guided_onboarding_example/public/components/step_one.tsx`.* + +The API service exposes an Observable that contains a boolean value for the state of a specific guide step. For example, if your plugin needs to check if the "Add data" step of the Security guide is currently active, you could use the following code snippet. + +``` +const { guidedOnboardingApi } = guidedOnboarding; +const isDataStepActive = useObservable(guidedOnboardingApi!.isGuideStepActive$('security', 'add_data')); +useEffect(() => { + // do some logic depending on the step state +}, [isDataStepActive]); +``` + +Alternatively, you can subscribe to the Observable directly. +``` +useEffect(() => { + const subscription = guidedOnboardingApi?.isGuideStepActive$('security', 'add_data').subscribe((isDataStepACtive) => { + // do some logic depending on the step state + }); + return () => subscription?.unsubscribe(); +}, [guidedOnboardingApi]); +``` + +### completeGuideStep(guideID: string, stepID: string): Promise\<{ state: GuidedOnboardingState } | undefined\> +The API service exposes an async function to mark a guide step as completed. +If the specified guide step is not currently active, the function is a noop. The return value is `undefined` in that case, +otherwise an updated `GuidedOnboardingState` is returned *(This is WIP and will likely change in the 8.6 dev cycle)*. + +``` +await guidedOnboardingApi?.completeGuideStep('security', 'add_data'); +``` + +## Guides config +To use the API service, you need to know a guide ID (one of `search`, `observability`, `security`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). Refer to guides config files in the folder `./public/constants` for more information. + + diff --git a/src/plugins/guided_onboarding/jest.config.js b/src/plugins/guided_onboarding/jest.config.js new file mode 100644 index 0000000000000..dbff307ed09e4 --- /dev/null +++ b/src/plugins/guided_onboarding/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/guided_onboarding'], + testRunner: 'jasmine2', + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/guided_onboarding', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/guided_onboarding/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts new file mode 100644 index 0000000000000..1ba565c9caee0 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiThemeComputed } from '@elastic/eui'; +import { css } from '@emotion/react'; + +/** + * + * Style overrides for the setup guide dropdown panel. + * There is currently no existing EUI component that fully supports what we need. + * In order to leverage a11y features, we are using the EuiFlyout and applying customizations + * See https://github.com/elastic/eui/issues/6241 for more details + */ +export const getGuidePanelStyles = (euiTheme: EuiThemeComputed) => ({ + flyoutOverrides: { + flyoutContainer: css` + top: 55px !important; + bottom: 25px !important; + right: 128px; + border-radius: 6px; + width: 480px; + height: auto; + animation: euiModal 350ms cubic-bezier(0.34, 1.61, 0.7, 1); + box-shadow: none; + "@media only screen and (max-width: 574px)": { + right: 25px; + width: 100%; + }, + `, + flyoutBody: css` + .euiFlyoutBody__overflowContent { + width: 480px; + padding-top: 10px; + } + `, + flyoutFooter: css` + border-radius: 0 0 6px 6px; + background: ${euiTheme.colors.ghost}; + padding: 24px 30px; + `, + }, +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx new file mode 100644 index 0000000000000..5eaf24163d2ae --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { act } from 'react-dom/test-utils'; +import React from 'react'; + +import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { HttpSetup } from '@kbn/core/public'; + +import { apiService } from '../services/api'; +import { guidesConfig } from '../constants/guides_config'; +import { GuidePanel } from './guide_panel'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; + +const applicationMock = applicationServiceMock.createStartContract(); + +const getGuidePanel = () => () => { + return ; +}; + +describe('GuidePanel', () => { + let httpClient: jest.Mocked; + let testBed: TestBed; + + beforeEach(async () => { + httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); + // Set default state on initial request (no active guides) + httpClient.get.mockResolvedValue({ + state: { activeGuide: 'unset', activeStep: 'unset' }, + }); + apiService.setup(httpClient); + + await act(async () => { + const GuidePanelComponent = getGuidePanel(); + testBed = registerTestBed(GuidePanelComponent)(); + }); + + testBed.component.update(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('it should be disabled in there is no active guide', async () => { + const { exists } = testBed; + expect(exists('disabledGuideButton')).toBe(true); + expect(exists('guideButton')).toBe(false); + expect(exists('guidePanel')).toBe(false); + }); + + test('it should be enabled if there is an active guide', async () => { + const { exists, component, find } = testBed; + + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState({ + activeGuide: 'search', + activeStep: guidesConfig.search.steps[0].id, + }); + }); + + component.update(); + + expect(exists('disabledGuideButton')).toBe(false); + expect(exists('guideButton')).toBe(true); + expect(exists('guidePanel')).toBe(true); + expect(find('guidePanelStep').length).toEqual(guidesConfig.search.steps.length); + }); +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx new file mode 100644 index 0000000000000..f32f55e42b340 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -0,0 +1,308 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useEffect, useRef } from 'react'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiButton, + EuiText, + EuiProgress, + EuiHorizontalRule, + EuiSpacer, + htmlIdGenerator, + EuiButtonEmpty, + EuiTitle, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + useEuiTheme, +} from '@elastic/eui'; + +import { ApplicationStart } from '@kbn/core-application-browser'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { guidesConfig } from '../constants/guides_config'; +import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types'; +import type { ApiService } from '../services/api'; + +import { GuideStep } from './guide_panel_step'; +import { getGuidePanelStyles } from './guide_panel.styles'; + +interface GuidePanelProps { + api: ApiService; + application: ApplicationStart; +} + +const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => { + if (state?.activeGuide && state.activeGuide !== 'unset') { + return guidesConfig[state.activeGuide]; + } + + return undefined; +}; + +const getCurrentStep = ( + steps?: StepConfig[], + state?: GuidedOnboardingState +): number | undefined => { + if (steps && state?.activeStep) { + const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep); + if (activeStepIndex > -1) { + return activeStepIndex + 1; + } + + return undefined; + } +}; + +const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => { + const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep); + + if (activeStepIndex < stepIndex) { + return 'incomplete'; + } + + if (activeStepIndex === stepIndex) { + return 'in_progress'; + } + + return 'complete'; +}; + +export const GuidePanel = ({ api, application }: GuidePanelProps) => { + const { euiTheme } = useEuiTheme(); + const [isGuideOpen, setIsGuideOpen] = useState(false); + const [guideState, setGuideState] = useState(undefined); + const isFirstRender = useRef(true); + + const styles = getGuidePanelStyles(euiTheme); + + const toggleGuide = () => { + setIsGuideOpen((prevIsGuideOpen) => !prevIsGuideOpen); + }; + + const navigateToStep = (step: StepConfig) => { + setIsGuideOpen(false); + if (step.location) { + application.navigateToApp(step.location.appID, { path: step.location.path }); + } + }; + + const navigateToLandingPage = () => { + setIsGuideOpen(false); + application.navigateToApp('home', { path: '#getting_started' }); + }; + + useEffect(() => { + const subscription = api.fetchGuideState$().subscribe((newState) => { + if ( + guideState?.activeGuide !== newState.activeGuide || + guideState?.activeStep !== newState.activeStep + ) { + if (isFirstRender.current) { + isFirstRender.current = false; + } else { + setIsGuideOpen(true); + } + } + setGuideState(newState); + }); + return () => subscription.unsubscribe(); + }, [api, guideState?.activeGuide, guideState?.activeStep]); + + const guideConfig = getConfig(guideState); + + // TODO handle loading, error state + // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 + if (!guideConfig) { + return ( + + {i18n.translate('guidedOnboarding.disabledGuidedSetupButtonLabel', { + defaultMessage: 'Setup guide', + })} + + ); + } + + const currentStep = getCurrentStep(guideConfig.steps, guideState); + + return ( + <> + + {currentStep + ? i18n.translate('guidedOnboarding.guidedSetupStepButtonLabel', { + defaultMessage: 'Setup guide: Step {currentStep}', + values: { + currentStep, + }, + }) + : i18n.translate('guidedOnboarding.guidedSetupButtonLabel', { + defaultMessage: 'Setup guide', + })} + + + {isGuideOpen && ( + + + + {i18n.translate('guidedOnboarding.dropdownPanel.backToGuidesLink', { + defaultMessage: 'Back to guides', + })} + + + +

{guideConfig?.title}

+
+ + + +
+ + +
+ +

{guideConfig?.description}

+
+ + {guideConfig.docs && ( + <> + + + + {guideConfig.docs.text} + + + + )} + + + + {/* + TODO: Progress bar should only show after the first step has been started + We need to make changes to the state itself in order to support this + */} + + + + + + + {guideConfig?.steps.map((step, index, steps) => { + const accordionId = htmlIdGenerator(`accordion${index}`)(); + const stepStatus = getStepStatus(steps, index, guideState?.activeStep); + + return ( + + ); + })} +
+
+ + + + + {/* TODO: Implement exit guide modal - https://github.com/elastic/kibana/issues/139804 */} + {}}> + {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { + defaultMessage: 'Exit setup guide', + })} + + + + + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedbackLabel', { + defaultMessage: 'feedback', + })} + + ), + }} + /> + + + + + + + {i18n.translate( + 'guidedOnboarding.dropdownPanel.footer.helpTextDescription', + { + defaultMessage: 'here to help', + } + )} + + ), + }} + /> + + + + +
+ )} + + ); +}; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts new file mode 100644 index 0000000000000..498059564e6ea --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.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 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 { EuiThemeComputed } from '@elastic/eui'; +import { css } from '@emotion/react'; + +export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed) => ({ + stepNumber: css` + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid ${euiTheme.colors.success}; + font-weight: ${euiTheme.font.weight.medium}; + text-align: center; + line-height: 1.4; + `, + stepTitle: css` + font-weight: ${euiTheme.font.weight.bold}; + `, +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx new file mode 100644 index 0000000000000..e6a300b6b6742 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { + EuiButton, + EuiText, + EuiAccordion, + EuiHorizontalRule, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + useEuiTheme, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import type { StepStatus, StepConfig } from '../types'; +import { getGuidePanelStepStyles } from './guide_panel_step.styles'; + +interface GuideStepProps { + accordionId: string; + stepStatus: StepStatus; + stepConfig: StepConfig; + stepNumber: number; + navigateToStep: (step: StepConfig) => void; +} + +export const GuideStep = ({ + accordionId, + stepStatus, + stepNumber, + stepConfig, + navigateToStep, +}: GuideStepProps) => { + const { euiTheme } = useEuiTheme(); + const styles = getGuidePanelStepStyles(euiTheme); + + const buttonContent = ( + + + {stepStatus === 'complete' ? ( + + ) : ( + {stepNumber} + )} + + + + {stepConfig.title} + + + + ); + + return ( +
+ + <> + + + +
    + {stepConfig.descriptionList.map((description, index) => { + return
  • {description}
  • ; + })} +
+
+ + + {stepStatus === 'in_progress' && ( + + + navigateToStep(stepConfig)} fill> + {/* TODO: Support for conditional "Continue" button label if user revists a step - https://github.com/elastic/kibana/issues/139752 */} + {i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { + defaultMessage: 'Start', + })} + + + + )} + +
+ + +
+ ); +}; diff --git a/src/plugins/guided_onboarding/public/components/index.ts b/src/plugins/guided_onboarding/public/components/index.ts index 131e5c328e97e..e13e9f105158a 100644 --- a/src/plugins/guided_onboarding/public/components/index.ts +++ b/src/plugins/guided_onboarding/public/components/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { GuidedOnboardingButton } from './onboarding_button'; +export { GuidePanel } from './guide_panel'; diff --git a/src/plugins/guided_onboarding/public/components/onboarding_button.tsx b/src/plugins/guided_onboarding/public/components/onboarding_button.tsx deleted file mode 100644 index bab27ec007a19..0000000000000 --- a/src/plugins/guided_onboarding/public/components/onboarding_button.tsx +++ /dev/null @@ -1,241 +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 React, { useState, useEffect, useRef } from 'react'; -import { css } from '@emotion/react'; -import { - EuiPopover, - EuiPopoverTitle, - EuiPopoverFooter, - EuiButton, - EuiText, - EuiProgress, - EuiAccordion, - EuiHorizontalRule, - EuiSpacer, - EuiTextColor, - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - useEuiTheme, - EuiButtonEmpty, - EuiTitle, -} from '@elastic/eui'; - -import { ApplicationStart } from '@kbn/core-application-browser'; -import { HttpStart } from '@kbn/core-http-browser'; -import { i18n } from '@kbn/i18n'; -import { guidesConfig } from '../constants'; -import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types'; -import type { ApiService } from '../services/api'; - -interface Props { - api: ApiService; - application: ApplicationStart; - http: HttpStart; -} - -const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => { - if (state?.activeGuide && state.activeGuide !== 'unset') { - return guidesConfig[state.activeGuide]; - } - - return undefined; -}; - -const getStepLabel = (steps?: StepConfig[], state?: GuidedOnboardingState): string => { - if (steps && state?.activeStep) { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep); - if (activeStepIndex > -1) { - return `: Step ${activeStepIndex + 1}`; - } - } - return ''; -}; - -const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep); - if (activeStepIndex < stepIndex) { - return 'incomplete'; - } - if (activeStepIndex === stepIndex) { - return 'in_progress'; - } - return 'complete'; -}; - -export const GuidedOnboardingButton = ({ api, application, http }: Props) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const [guidedOnboardingState, setGuidedOnboardingState] = useState< - GuidedOnboardingState | undefined - >(undefined); - - const firstRender = useRef(true); - - useEffect(() => { - const subscription = api.fetchGuideState$().subscribe((newState) => { - if ( - guidedOnboardingState?.activeGuide !== newState.activeGuide || - guidedOnboardingState?.activeStep !== newState.activeStep - ) { - if (firstRender.current) { - firstRender.current = false; - } else { - setIsPopoverOpen(true); - } - } - setGuidedOnboardingState(newState); - }); - return () => subscription.unsubscribe(); - }, [api, guidedOnboardingState?.activeGuide, guidedOnboardingState?.activeStep]); - - const { euiTheme } = useEuiTheme(); - - const togglePopover = () => { - setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen); - }; - - const popoverContainerCss = css` - width: 400px; - `; - - const statusCircleCss = ({ status }: { status: StepStatus }) => css` - width: 24px; - height: 24px; - border-radius: 32px; - ${(status === 'complete' || status === 'in_progress') && - `background-color: ${euiTheme.colors.success};`} - ${status === 'incomplete' && - ` - border: 2px solid ${euiTheme.colors.lightShade}; - `} - `; - - const guideConfig = getConfig(guidedOnboardingState); - const stepLabel = getStepLabel(guideConfig?.steps, guidedOnboardingState); - - const navigateToStep = (step: StepConfig) => { - setIsPopoverOpen(false); - if (step.location) { - application.navigateToApp(step.location.appID, { path: step.location.path }); - } - }; - - return guideConfig ? ( - - {i18n.translate('guidedOnboarding.guidedSetupButtonLabel', { - defaultMessage: 'Guided setup{stepLabel}', - values: { - stepLabel, - }, - })} -
- } - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - anchorPosition="downRight" - hasArrow={false} - offset={10} - panelPaddingSize="l" - > - - {}} - iconSide="left" - iconType="arrowLeft" - isDisabled={true} - flush="left" - > - {i18n.translate('guidedOnboarding.dropdownPanel.backToGuidesLink', { - defaultMessage: 'Back to guides', - })} - - -

{guideConfig?.title}

-
-
- -
- -

{guideConfig?.description}

-
- - - - - {guideConfig?.steps.map((step, index, steps) => { - const accordionId = htmlIdGenerator(`accordion${index}`)(); - - const stepStatus = getStepStatus(steps, index, guidedOnboardingState?.activeStep); - const buttonContent = ( - - - - {stepStatus} - {stepStatus === 'complete' && } - - - {step.title} - - ); - - return ( -
- - <> - - {step.description} - - {stepStatus === 'in_progress' && ( - - - navigateToStep(step)} fill> - {/* TODO: Support for conditional "Continue" button label if user revists a step */} - {i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { - defaultMessage: 'Start', - })} - - - - )} - - - - {/* Do not show horizontal rule for last item */} - {guideConfig.steps.length - 1 !== index && } -
- ); - })} - - - -

- {i18n.translate('guidedOnboarding.dropdownPanel.footerDescription', { - defaultMessage: `Got questions? We're here to help.`, - })} -

-
-
-
-
- - ) : ( - - Guided setup - - ); -}; diff --git a/src/plugins/guided_onboarding/public/constants/index.ts b/src/plugins/guided_onboarding/public/constants/guides_config.ts similarity index 84% rename from src/plugins/guided_onboarding/public/constants/index.ts rename to src/plugins/guided_onboarding/public/constants/guides_config.ts index 700f035c8c8dd..0cbee9d4b12b6 100644 --- a/src/plugins/guided_onboarding/public/constants/index.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config.ts @@ -6,14 +6,10 @@ * Side Public License, v 1. */ +import { GuidesConfig } from '../types'; import { securityConfig } from './security'; import { observabilityConfig } from './observability'; import { searchConfig } from './search'; -import type { GuideConfig, UseCase } from '../types'; - -type GuidesConfig = { - [key in UseCase]: GuideConfig; -}; export const guidesConfig: GuidesConfig = { security: securityConfig, diff --git a/src/plugins/guided_onboarding/public/constants/observability.ts b/src/plugins/guided_onboarding/public/constants/observability.ts index 72d2a70dfa44d..3f96ad1268173 100644 --- a/src/plugins/guided_onboarding/public/constants/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/observability.ts @@ -20,38 +20,29 @@ export const observabilityConfig: GuideConfig = { { id: 'add_data', title: 'Add data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { - id: 'rules', - title: 'Customize your alerting rules', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'view_dashboard', + title: 'View Kubernetes metrics', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { - id: 'infrastructure', - title: 'View infrastructure details', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'explore', - title: 'Explore Discover and Dashboards', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'tour', - title: 'Tour Observability', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'do_more', - title: 'Do more with Observability', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'tour_observability', + title: 'Tour Elastic Observability', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/constants/search.ts b/src/plugins/guided_onboarding/public/constants/search.ts index 8f6ff8fb7600b..b4c3c151aca0c 100644 --- a/src/plugins/guided_onboarding/public/constants/search.ts +++ b/src/plugins/guided_onboarding/public/constants/search.ts @@ -10,7 +10,7 @@ import type { GuideConfig } from '../types'; export const searchConfig: GuideConfig = { title: 'Search my data', - description: `We'll help you build world-class search experiences with your data.`, + description: `We'll help you build world-class search experiences with your data, using Elastic's out-of-the-box web crawler, connectors, and our robust APIs. Gain deep insights from the built-in search analytics and use that data to inform changes to relevance.`, docs: { text: 'Enterprise Search 101 Documentation', url: 'example.com', @@ -19,34 +19,37 @@ export const searchConfig: GuideConfig = { { id: 'add_data', title: 'Add data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], location: { appID: 'guidedOnboardingExample', path: 'stepOne', }, }, { - id: 'search_experience', - title: 'Build a search experience', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'browse_docs', + title: 'Browse your documents', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], location: { appID: 'guidedOnboardingExample', path: 'stepTwo?showTour=true', }, }, { - id: 'optimize', - title: 'Optimize your search relevance', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'review', - title: 'Review your search analytics', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'search_experience', + title: 'Build a search experience', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/constants/security.ts b/src/plugins/guided_onboarding/public/constants/security.ts index f8447f81dc019..2c19e7acc2bed 100644 --- a/src/plugins/guided_onboarding/public/constants/security.ts +++ b/src/plugins/guided_onboarding/public/constants/security.ts @@ -16,28 +16,38 @@ export const securityConfig: GuideConfig = { { id: 'add_data', title: 'Add and view your data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'rules', title: 'Turn on rules', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'alerts', title: 'View Alerts', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'cases', title: 'Cases and investigations', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - }, - { - id: 'do_more', - title: 'Do more with Elastic Security', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index 9b2e95656cb80..902acaa899e3a 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -16,7 +16,6 @@ import { Plugin, CoreTheme, ApplicationStart, - HttpStart, PluginInitializerContext, } from '@kbn/core/public'; @@ -26,7 +25,7 @@ import { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart, } from './types'; -import { GuidedOnboardingButton } from './components'; +import { GuidePanel } from './components'; import { ApiService, apiService } from './services/api'; export class GuidedOnboardingPlugin @@ -56,7 +55,6 @@ export class GuidedOnboardingPlugin theme$: theme.theme$, api: apiService, application, - http, }), }); @@ -73,18 +71,16 @@ export class GuidedOnboardingPlugin theme$, api, application, - http, }: { targetDomElement: HTMLElement; theme$: Rx.Observable; api: ApiService; application: ApplicationStart; - http: HttpStart; }) { ReactDOM.render( - + , targetDomElement diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts new file mode 100644 index 0000000000000..9f5e20cb9f89d --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { firstValueFrom, Subscription } from 'rxjs'; + +import { API_BASE_PATH } from '../../common'; +import { ApiService } from './api'; +import { GuidedOnboardingState } from '..'; +import { guidesConfig } from '../constants/guides_config'; + +const searchGuide = 'search'; +const firstStep = guidesConfig[searchGuide].steps[0].id; +const secondStep = guidesConfig[searchGuide].steps[1].id; +const lastStep = guidesConfig[searchGuide].steps[2].id; + +describe('GuidedOnboarding ApiService', () => { + let httpClient: jest.Mocked; + let apiService: ApiService; + let subscription: Subscription; + + beforeEach(() => { + httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); + httpClient.get.mockResolvedValue({ + state: { activeGuide: searchGuide, activeStep: firstStep }, + }); + apiService = new ApiService(); + apiService.setup(httpClient); + }); + + afterEach(() => { + if (subscription) { + subscription.unsubscribe(); + } + jest.restoreAllMocks(); + }); + + describe('fetchGuideState$', () => { + it('sends a request to the get API', () => { + subscription = apiService.fetchGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`); + }); + + it('broadcasts the updated state', async () => { + await apiService.updateGuideState({ + activeGuide: searchGuide, + activeStep: secondStep, + }); + + const state = await firstValueFrom(apiService.fetchGuideState$()); + expect(state).toEqual({ activeGuide: searchGuide, activeStep: secondStep }); + }); + }); + + describe('updateGuideState', () => { + it('sends a request to the put API', async () => { + const state = { + activeGuide: searchGuide, + activeStep: secondStep, + }; + await apiService.updateGuideState(state as GuidedOnboardingState); + expect(httpClient.put).toHaveBeenCalledTimes(1); + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify(state), + }); + }); + }); + + describe('isGuideStepActive$', () => { + it('returns true if the step is active', async (done) => { + subscription = apiService + .isGuideStepActive$(searchGuide, firstStep) + .subscribe((isStepActive) => { + if (isStepActive) { + done(); + } + }); + }); + + it('returns false if the step is not active', async (done) => { + subscription = apiService + .isGuideStepActive$(searchGuide, secondStep) + .subscribe((isStepActive) => { + if (!isStepActive) { + done(); + } + }); + }); + }); + + describe('completeGuideStep', () => { + it(`completes the step when it's active`, async () => { + await apiService.completeGuideStep(searchGuide, firstStep); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config, we are checking for the next step + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + activeGuide: searchGuide, + activeStep: secondStep, + }), + }); + }); + + it(`completes the guide when the last step is active`, async () => { + httpClient.get.mockResolvedValue({ + // this state depends on the guides config + state: { activeGuide: searchGuide, activeStep: lastStep }, + }); + apiService.setup(httpClient); + + await apiService.completeGuideStep(searchGuide, lastStep); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config, we are checking for the last step + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + activeGuide: searchGuide, + activeStep: 'completed', + }), + }); + }); + + it(`does nothing if the step is not active`, async () => { + await apiService.completeGuideStep(searchGuide, secondStep); + expect(httpClient.put).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 543b4ffc30f41..b99975c3a837a 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -7,10 +7,11 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BehaviorSubject, map, from, concatMap, of } from 'rxjs'; +import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import { API_BASE_PATH } from '../../common'; -import { GuidedOnboardingState } from '../types'; +import { GuidedOnboardingState, UseCase } from '../types'; +import { getNextStep, isLastStep } from './helpers'; export class ApiService { private client: HttpSetup | undefined; @@ -21,7 +22,12 @@ export class ApiService { this.onboardingGuideState$ = new BehaviorSubject(undefined); } - public fetchGuideState$() { + /** + * An Observable with the guided onboarding state. + * Initially the state is fetched from the backend. + * Subsequently, the observable is updated automatically, when the state changes. + */ + public fetchGuideState$(): Observable { // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => @@ -34,7 +40,14 @@ export class ApiService { ); } - public async updateGuideState(newState: GuidedOnboardingState) { + /** + * Updates the state of the guided onboarding + * @param {GuidedOnboardingState} newState the new state of the guided onboarding + * @return {Promise} a promise with the updated state or undefined if the update fails + */ + public async updateGuideState( + newState: GuidedOnboardingState + ): Promise<{ state: GuidedOnboardingState } | undefined> { if (!this.client) { throw new Error('ApiService has not be initialized.'); } @@ -54,6 +67,51 @@ export class ApiService { console.error(error); } } + + /** + * An observable with the boolean value if the step is active. + * Returns true, if the passed params identify the guide step that is currently active. + * Returns false otherwise. + * @param {string} guideID the id of the guide (one of search, observability, security) + * @param {string} stepID the id of the step in the guide + * @return {Observable} an observable with the boolean value + */ + public isGuideStepActive$(guideID: string, stepID: string): Observable { + return this.fetchGuideState$().pipe( + map((state) => { + return state ? state.activeGuide === guideID && state.activeStep === stepID : false; + }) + ); + } + + /** + * Completes the guide step identified by the passed params. + * A noop if the passed step is not active. + * Completes the current guide, if the step is the last one in the guide. + * @param {string} guideID the id of the guide (one of search, observability, security) + * @param {string} stepID the id of the step in the guide + * @return {Promise} a promise with the updated state or undefined if the operation fails + */ + public async completeGuideStep( + guideID: string, + stepID: string + ): Promise<{ state: GuidedOnboardingState } | undefined> { + const isStepActive = await firstValueFrom(this.isGuideStepActive$(guideID, stepID)); + if (isStepActive) { + if (isLastStep(guideID, stepID)) { + await this.updateGuideState({ activeGuide: guideID as UseCase, activeStep: 'completed' }); + } else { + const nextStepID = getNextStep(guideID, stepID); + if (nextStepID !== undefined) { + await this.updateGuideState({ + activeGuide: guideID as UseCase, + activeStep: nextStepID, + }); + } + } + } + return undefined; + } } export const apiService = new ApiService(); diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts new file mode 100644 index 0000000000000..6e1a3cc3e0049 --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { guidesConfig } from '../constants/guides_config'; +import { getNextStep, isLastStep } from './helpers'; + +const searchGuide = 'search'; +const firstStep = guidesConfig[searchGuide].steps[0].id; +const secondStep = guidesConfig[searchGuide].steps[1].id; +const lastStep = guidesConfig[searchGuide].steps[2].id; + +describe('GuidedOnboarding ApiService helpers', () => { + // this test suite depends on the guides config + describe('isLastStepActive', () => { + it('returns true if the passed params are for the last step', () => { + const result = isLastStep(searchGuide, lastStep); + expect(result).toBe(true); + }); + + it('returns false if the passed params are not for the last step', () => { + const result = isLastStep(searchGuide, firstStep); + expect(result).toBe(false); + }); + }); + + describe('getNextStep', () => { + it('returns id of the next step', () => { + const result = getNextStep(searchGuide, firstStep); + expect(result).toEqual(secondStep); + }); + + it('returns undefined if the params are not part of the config', () => { + const result = getNextStep('some_guide', 'some_step'); + expect(result).toBeUndefined(); + }); + + it(`returns undefined if it's the last step`, () => { + const result = getNextStep(searchGuide, lastStep); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts new file mode 100644 index 0000000000000..3eb0bfca9b751 --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { guidesConfig } from '../constants/guides_config'; +import { GuideConfig, StepConfig, UseCase } from '../types'; + +export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { + if (guideID && Object.keys(guidesConfig).includes(guideID)) { + return guidesConfig[guideID as UseCase]; + } +}; + +const getStepIndex = (guideID: string, stepID: string): number => { + const guide = getGuideConfig(guideID); + if (guide) { + return guide.steps.findIndex((step: StepConfig) => step.id === stepID); + } + return -1; +}; + +export const isLastStep = (guideID: string, stepID: string): boolean => { + const guide = getGuideConfig(guideID); + const activeStepIndex = getStepIndex(guideID, stepID); + const stepsNumber = guide?.steps.length || 0; + if (stepsNumber > 0) { + return activeStepIndex === stepsNumber - 1; + } + return false; +}; + +export const getNextStep = (guideID: string, stepID: string): string | undefined => { + const guide = getGuideConfig(guideID); + const activeStepIndex = getStepIndex(guideID, stepID); + if (activeStepIndex > -1 && guide?.steps[activeStepIndex + 1]) { + return guide?.steps[activeStepIndex + 1].id; + } +}; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 8beb1772a511e..7925fa8ae69d7 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -26,7 +26,7 @@ export type StepStatus = 'incomplete' | 'complete' | 'in_progress'; export interface StepConfig { id: string; title: string; - description: string; + descriptionList: string[]; location?: { appID: string; path: string; @@ -44,9 +44,13 @@ export interface GuideConfig { steps: StepConfig[]; } +export type GuidesConfig = { + [key in UseCase]: GuideConfig; +}; + export interface GuidedOnboardingState { activeGuide: UseCase | 'unset'; - activeStep: string | 'unset'; + activeStep: string | 'unset' | 'completed'; } export interface ClientConfigType { diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index cc80adc4cb463..41df488839358 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -438,6 +438,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmEnableServiceMetrics': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'banners:placement': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, @@ -558,10 +562,6 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'enterpriseSearch:enableIndexTransformsTab': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'enterpriseSearch:enableBehavioralAnalyticsSection': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 924649351d988..2bd59dc69084f 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -42,6 +42,7 @@ export interface UsageStats { 'observability:maxSuggestions': number; 'observability:enableComparisonByDefault': boolean; 'observability:enableServiceGroups': boolean; + 'observability:apmEnableServiceMetrics': boolean; 'observability:enableInfrastructureHostsView': boolean; 'visualize:enableLabs': boolean; 'visualization:heatmap:maxBuckets': number; @@ -149,6 +150,5 @@ export interface UsageStats { 'securitySolution:enableGroupedNav': boolean; 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; - 'enterpriseSearch:enableIndexTransformsTab': boolean; 'enterpriseSearch:enableBehavioralAnalyticsSection': boolean; } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index a1ddc4ee56040..1a97586dffa62 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8702,6 +8702,12 @@ "description": "Non-default value of setting." } }, + "observability:apmEnableServiceMetrics": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "banners:placement": { "type": "keyword", "_meta": { @@ -8882,12 +8888,6 @@ "description": "Non-default value of setting." } }, - "enterpriseSearch:enableIndexTransformsTab": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "enterpriseSearch:enableBehavioralAnalyticsSection": { "type": "boolean", "_meta": { @@ -10323,4 +10323,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/plugins/unified_search/kibana.json b/src/plugins/unified_search/kibana.json index 07e438ab52174..0dc0627ea6f4b 100755 --- a/src/plugins/unified_search/kibana.json +++ b/src/plugins/unified_search/kibana.json @@ -11,6 +11,7 @@ "ui": true, "requiredPlugins": ["dataViews", "data", "uiActions", "screenshotMode"], "requiredBundles": ["kibanaUtils", "kibanaReact", "data"], + "optionalPlugins": ["usageCollection"], "serviceFolders": ["autocomplete"], "configPath": ["unifiedSearch"] } diff --git a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx index 30b4d993e6954..40d3abfb7fae0 100644 --- a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx +++ b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx @@ -566,4 +566,46 @@ storiesOf('SearchBar', module) }, isDisabled: true, } as SearchBarProps) + ) + .add('no submit button', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + showSubmitButton: false, + } as SearchBarProps) + ) + .add('submit button always as icon', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + submitButtonStyle: 'iconOnly', + } as SearchBarProps) + ) + .add('submit button always as a full button', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + submitButtonStyle: 'full', + } as SearchBarProps) ); diff --git a/src/plugins/unified_search/public/plugin.ts b/src/plugins/unified_search/public/plugin.ts index e853e6b77e8e1..645652cd60f34 100755 --- a/src/plugins/unified_search/public/plugin.ts +++ b/src/plugins/unified_search/public/plugin.ts @@ -53,6 +53,7 @@ export class UnifiedSearchPublicPlugin ); uiActions.registerAction(createUpdateFilterReferencesAction(query.filterManager)); + this.usageCollection = usageCollection; return { autocomplete: this.autocomplete.setup(core, { diff --git a/src/plugins/unified_search/public/query_string_input/index.tsx b/src/plugins/unified_search/public/query_string_input/index.tsx index 536df031edaa7..a92295b646535 100644 --- a/src/plugins/unified_search/public/query_string_input/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/index.tsx @@ -7,8 +7,7 @@ */ import React from 'react'; -import { AggregateQuery, Query } from '@kbn/es-query'; -import { withKibana } from '@kbn/kibana-react-plugin/public'; +import type { AggregateQuery, Query } from '@kbn/es-query'; import type { QueryBarTopRowProps } from './query_bar_top_row'; import type { QueryStringInputProps } from './query_string_input'; @@ -24,7 +23,7 @@ export const QueryBarTopRow = ( ); -const LazyQueryStringInputUI = withKibana(React.lazy(() => import('./query_string_input'))); +const LazyQueryStringInputUI = React.lazy(() => import('./query_string_input')); export const QueryStringInput = (props: QueryStringInputProps) => ( }> diff --git a/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx b/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx index 591fe94360793..2289876fcea73 100644 --- a/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx @@ -8,24 +8,14 @@ import React from 'react'; import { QueryLanguageSwitcher, QueryLanguageSwitcherProps } from './language_switcher'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { coreMock } from '@kbn/core/public/mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiButtonIcon, EuiIcon, EuiPopover } from '@elastic/eui'; const startMock = coreMock.createStart(); describe('LanguageSwitcher', () => { - function wrapInContext(testProps: QueryLanguageSwitcherProps) { - const services = { - uiSettings: startMock.uiSettings, - docLinks: startMock.docLinks, - }; - - return ( - - - - ); + function wrapInContext(testProps: Omit) { + return ; } it('should select the lucene context menu if language is lucene', () => { diff --git a/src/plugins/unified_search/public/query_string_input/language_switcher.tsx b/src/plugins/unified_search/public/query_string_input/language_switcher.tsx index edd2028fda48c..0c369b4efc077 100644 --- a/src/plugins/unified_search/public/query_string_input/language_switcher.tsx +++ b/src/plugins/unified_search/public/query_string_input/language_switcher.tsx @@ -18,7 +18,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { DocLinksStart } from '@kbn/core/public'; export interface QueryLanguageSwitcherProps { language: string; @@ -27,6 +27,9 @@ export interface QueryLanguageSwitcherProps { nonKqlMode?: 'lucene' | 'text'; isOnTopBarMenu?: boolean; isDisabled?: boolean; + deps: { + docLinks: DocLinksStart; + }; } export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ @@ -36,9 +39,9 @@ export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ nonKqlMode = 'lucene', isOnTopBarMenu, isDisabled, + deps: { docLinks }, }: QueryLanguageSwitcherProps) { - const kibana = useKibana(); - const kueryQuerySyntaxDocs = kibana.services.docLinks!.links.query.kueryQuerySyntax; + const kueryQuerySyntaxDocs = docLinks.links.query.kueryQuerySyntax; const [isPopoverOpen, setIsPopoverOpen] = useState(false); const button = ( diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx index 4a921c3a1d177..c36124ca7f448 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -492,6 +492,9 @@ export function QueryBarMenuPanels({ onSelectLanguage={onSelectLanguage} nonKqlMode={nonKqlMode} isOnTopBarMenu={true} + deps={{ + docLinks: kibana.services.docLinks, + }} /> ), }, diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 052e0ab7b32c8..7be320d5d7aeb 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -208,6 +208,23 @@ describe('QueryBarTopRowTopRow', () => { expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1); }); + it('Should render update button as icon button', () => { + const component = mount( + wrapQueryBarTopRowInContext({ + isDirty: false, + screenTitle: 'Another Screen', + showDatePicker: true, + showSubmitButton: true, + submitButtonStyle: 'iconOnly', + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + timeHistory: mockTimeHistory, + }) + ); + + expect(component.find(REFRESH_BUTTON_SELECTOR).prop('iconOnly')).toBe(true); + }); + it('Should render the timefilter duration container for sharing', () => { const component = mount( wrapQueryBarTopRowInContext({ diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index c0848f630daa8..0780b05778ca0 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -15,6 +15,7 @@ import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; import { getAggregateQueryMode, isOfQueryType, isOfAggregateQueryType } from '@kbn/es-query'; import { EMPTY } from 'rxjs'; import { map } from 'rxjs/operators'; +import { throttle } from 'lodash'; import { EuiFlexGroup, EuiFlexItem, @@ -30,7 +31,7 @@ import { TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; -import { useKibana, withKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { IUnifiedSearchPluginServices } from '../types'; import QueryStringInputUI from './query_string_input'; @@ -51,8 +52,6 @@ const SuperDatePicker = React.memo( EuiSuperDatePicker as any ) as unknown as typeof EuiSuperDatePicker; -const QueryStringInput = withKibana(QueryStringInputUI); - // @internal export interface QueryBarTopRowProps { customSubmitButton?: any; @@ -93,6 +92,13 @@ export interface QueryBarTopRowProps filterBar?: React.ReactNode; showDatePickerAsBadge?: boolean; showSubmitButton?: boolean; + /** + * Style of the submit button + * `iconOnly` - use IconButton + * `full` - use SuperUpdateButton + * (default) `auto` - `iconOnly` on smaller screens, and `full` on larger screens + */ + submitButtonStyle?: 'auto' | 'iconOnly' | 'full'; suggestionsSize?: SuggestionsListSize; isScreenshotMode?: boolean; onTextLangQuerySubmit: (query?: Query | AggregateQuery) => void; @@ -142,18 +148,23 @@ export const QueryBarTopRow = React.memo( const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); const [codeEditorIsExpanded, setCodeEditorIsExpanded] = useState(false); + const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] = + props.submitButtonStyle ?? 'auto'; + const submitButtonIconOnly = + submitButtonStyle === 'auto' ? !isXXLarge : submitButtonStyle === 'iconOnly'; useEffect(() => { - function handleResize() { + if (submitButtonStyle !== 'auto') return; + + const handleResize = throttle(() => { setIsXXLarge(window.innerWidth >= 1440); - } + }, 50); - window.removeEventListener('resize', handleResize); window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); - }, []); + }, [submitButtonStyle]); const { showQueryInput = true, @@ -166,7 +177,19 @@ export const QueryBarTopRow = React.memo( const [isQueryInputFocused, setIsQueryInputFocused] = useState(false); const kibana = useKibana(); - const { uiSettings, storage, appName } = kibana.services; + + const { + uiSettings, + storage, + appName, + data, + usageCollection, + unifiedSearch, + notifications, + docLinks, + http, + } = kibana.services; + const isQueryLangSelected = props.query && !isOfQueryType(props.query); const queryLanguage = props.query && isOfQueryType(props.query) && props.query.language; @@ -404,7 +427,7 @@ export const QueryBarTopRow = React.memo( - )} diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx index 41060aaecb3df..b0dcdac7a421c 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx @@ -20,13 +20,12 @@ import { render } from '@testing-library/react'; import { EuiTextArea, EuiIcon } from '@elastic/eui'; -import { QueryLanguageSwitcher } from './language_switcher'; -import QueryStringInputUI from './query_string_input'; - import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; -import { KibanaContextProvider, withKibana } from '@kbn/kibana-react-plugin/public'; + +import { QueryLanguageSwitcher } from './language_switcher'; +import QueryStringInput from './query_string_input'; import { unifiedSearchPluginMock } from '../mocks'; jest.useFakeTimers(); @@ -63,27 +62,25 @@ const createMockStorage = () => ({ clear: jest.fn(), }); -const QueryStringInput = withKibana(QueryStringInputUI); - function wrapQueryStringInputInContext(testProps: any, storage?: any) { - const services = { - ...startMock, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), - appName: testProps.appName || 'test', - storage: storage || createMockStorage(), - }; - const defaultOptions = { screenTitle: 'Another Screen', intl: null as any, + deps: { + unifiedSearch: unifiedSearchPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), + appName: testProps.appName || 'test', + storage: storage || createMockStorage(), + usageCollection: { reportUiCounter: () => {} }, + uiSettings: startMock.uiSettings, + http: startMock.http, + docLinks: startMock.docLinks, + }, }; return ( - - - + ); } @@ -200,7 +197,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -341,7 +338,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -379,7 +376,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); input!.value = 'foo\u00A0bar'; diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx index 84a12d8c63200..235173dc4c23c 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx @@ -28,27 +28,41 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { compact, debounce, isEmpty, isEqual, isFunction } from 'lodash'; -import { Toast } from '@kbn/core/public'; +import { CoreStart, DocLinksStart, Toast } from '@kbn/core/public'; import type { Query } from '@kbn/es-query'; -import { getQueryLog } from '@kbn/data-plugin/public'; +import { DataPublicPluginStart, getQueryLog } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; import { getFieldSubtypeNested, KIBANA_USER_QUERY_LANGUAGE_KEY } from '@kbn/data-plugin/common'; -import { KibanaReactContextValue, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { matchPairs } from './match_pairs'; import { toUser } from './to_user'; import { fromUser } from './from_user'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; import type { SuggestionsListSize } from '../typeahead/suggestions_component'; -import type { IUnifiedSearchPluginServices } from '../types'; import { SuggestionsComponent } from '../typeahead'; import { onRaf } from '../utils'; import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; -import { QuerySuggestion, QuerySuggestionTypes } from '../autocomplete'; +import { AutocompleteService, QuerySuggestion, QuerySuggestionTypes } from '../autocomplete'; import { getTheme } from '../services'; import './query_string_input.scss'; +export interface QueryStringInputDependencies { + unifiedSearch: { + autocomplete: ReturnType; + }; + usageCollection?: UsageCollectionStart; + data: DataPublicPluginStart; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; +} + export interface QueryStringInputProps { indexPatterns: Array; query: Query; @@ -72,6 +86,8 @@ export interface QueryStringInputProps { isClearable?: boolean; iconType?: EuiIconProps['type']; isDisabled?: boolean; + appName: string; + deps: QueryStringInputDependencies; /** * @param nonKqlMode by default if language switch is enabled, user can switch between kql and lucene syntax mode @@ -93,10 +109,6 @@ export interface QueryStringInputProps { timeRangeForSuggestionsOverride?: boolean; } -interface Props extends QueryStringInputProps { - kibana: KibanaReactContextValue; -} - interface State { isSuggestionsVisible: boolean; index: number | null; @@ -126,7 +138,7 @@ const KEY_CODES = { // Needed for React.lazy // eslint-disable-next-line import/no-default-export -export default class QueryStringInputUI extends PureComponent { +export default class QueryStringInputUI extends PureComponent { static defaultProps = { storageKey: KIBANA_USER_QUERY_LANGUAGE_KEY, iconType: 'search', @@ -149,10 +161,10 @@ export default class QueryStringInputUI extends PureComponent { private persistedLog: PersistedLog | undefined; private abortController?: AbortController; private fetchIndexPatternsAbortController?: AbortController; - private services = this.props.kibana.services; - private reportUiCounter = this.services.usageCollection?.reportUiCounter.bind( - this.services.usageCollection, - this.services.appName + + private reportUiCounter = this.props.deps.usageCollection?.reportUiCounter.bind( + this.props.deps.usageCollection, + this.props.appName ); private componentIsUnmounting = false; @@ -181,7 +193,7 @@ export default class QueryStringInputUI extends PureComponent { const currentAbortController = this.fetchIndexPatternsAbortController; const objectPatternsFromStrings = (await fetchIndexPatterns( - this.services.data.indexPatterns, + this.props.deps.data.indexPatterns, stringPatterns )) as DataView[]; @@ -203,9 +215,9 @@ export default class QueryStringInputUI extends PureComponent { const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const hasQuerySuggestions = await this.services.unifiedSearch.autocomplete.hasQuerySuggestions( - language - ); + + const hasQuerySuggestions = + this.props.deps.unifiedSearch.autocomplete.hasQuerySuggestions(language); if ( !hasQuerySuggestions || @@ -226,7 +238,7 @@ export default class QueryStringInputUI extends PureComponent { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); const suggestions = - (await this.services.unifiedSearch.autocomplete.getQuerySuggestions({ + (await this.props.deps.unifiedSearch.autocomplete.getQuerySuggestions({ language, indexPatterns, query: queryString, @@ -456,13 +468,13 @@ export default class QueryStringInputUI extends PureComponent { if ( subTypeNested && subTypeNested.nested && - !this.services.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') + !this.props.deps.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') ) { - const { notifications, docLinks } = this.services; + const { notifications, docLinks } = this.props.deps; const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { - if (!this.services.storage) return; - this.services.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); + if (!this.props.deps.storage) return; + this.props.deps.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); notifications!.toasts.remove(toast); }; @@ -536,12 +548,12 @@ export default class QueryStringInputUI extends PureComponent { // Send telemetry info every time the user opts in or out of kuery // As a result it is important this function only ever gets called in the // UI component's change handler. - this.services.http.post('/api/kibana/kql_opt_in_stats', { + this.props.deps.http.post('/api/kibana/kql_opt_in_stats', { body: JSON.stringify({ opt_in: language === 'kuery' }), }); const storageKey = this.props.storageKey; - this.services.storage.set(storageKey!, language); + this.props.deps.storage.set(storageKey!, language); const newQuery = { query: '', language }; this.onChange(newQuery); @@ -597,10 +609,11 @@ export default class QueryStringInputUI extends PureComponent { }; private initPersistedLog = () => { - const { uiSettings, storage, appName } = this.services; + const { uiSettings } = this.props.deps; + const { appName } = this.props; this.persistedLog = this.props.persistedLog ? this.props.persistedLog - : getQueryLog(uiSettings, storage, appName, this.props.query.language); + : getQueryLog(uiSettings, this.props.deps.storage, appName, this.props.query.language); }; public onMouseEnterSuggestion = (suggestion: QuerySuggestion, index: number) => { @@ -622,7 +635,7 @@ export default class QueryStringInputUI extends PureComponent { window.addEventListener('resize', this.handleAutoHeight); } - public componentDidUpdate(prevProps: Props) { + public componentDidUpdate(prevProps: QueryStringInputProps) { const parsedQuery = fromUser(toUser(this.props.query.query)); if (!isEqual(this.props.query.query, parsedQuery)) { this.onChange({ ...this.props.query, query: parsedQuery }); @@ -721,6 +734,9 @@ export default class QueryStringInputUI extends PureComponent { anchorPosition={this.props.languageSwitcherPopoverAnchorPosition} onSelectLanguage={this.onSelectLanguage} nonKqlMode={this.props.nonKqlMode} + deps={{ + docLinks: this.props.deps.docLinks, + }} /> ); @@ -748,7 +764,7 @@ export default class QueryStringInputUI extends PureComponent { style={{ position: 'relative', width: '100%' }} aria-label={i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { defaultMessage: 'Search and filter the {pageType} page', - values: { pageType: this.services.appName }, + values: { pageType: this.props.appName }, })} aria-haspopup="true" aria-expanded={this.state.isSuggestionsVisible} @@ -777,7 +793,7 @@ export default class QueryStringInputUI extends PureComponent { spellCheck={false} aria-label={i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { defaultMessage: 'Start typing to search and filter the {pageType} page', - values: { pageType: this.services.appName }, + values: { pageType: this.props.appName }, })} aria-autocomplete="list" aria-controls={this.state.isSuggestionsVisible ? 'kbnTypeahead__items' : undefined} diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index a4df4d0f1a76e..5c0dfda5d9b2c 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -193,6 +193,8 @@ export function createSearchBar({ showQueryBar={props.showQueryBar} showQueryInput={props.showQueryInput} showSaveQuery={props.showSaveQuery} + showSubmitButton={props.showSubmitButton} + submitButtonStyle={props.submitButtonStyle} isDisabled={props.isDisabled} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 448eaf7aba7b9..76746d8a86979 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -26,7 +26,7 @@ import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu'; import type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from '../dataview_picker'; -import QueryBarTopRow from '../query_string_input/query_bar_top_row'; +import QueryBarTopRow, { QueryBarTopRowProps } from '../query_string_input/query_bar_top_row'; import { FilterBar, FilterItems } from '../filter_bar'; import type { SuggestionsListSize } from '../typeahead/suggestions_component'; import { searchBarStyles } from './search_bar.styles'; @@ -93,6 +93,7 @@ export interface SearchBarOwnProps { textBasedLanguageModeErrors?: Error[]; onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; showSubmitButton?: boolean; + submitButtonStyle?: QueryBarTopRowProps['submitButtonStyle']; // defines size of suggestions query popover suggestionsSize?: SuggestionsListSize; isScreenshotMode?: boolean; @@ -544,6 +545,7 @@ class SearchBarUI extends C this.props.customSubmitButton ? this.props.customSubmitButton : undefined } showSubmitButton={this.props.showSubmitButton} + submitButtonStyle={this.props.submitButtonStyle} dataTestSubj={this.props.dataTestSubj} indicateNoData={this.props.indicateNoData} placeholder={this.props.placeholder} diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 246fc87114db4..d079cd72edf81 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -13,7 +13,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { Query, AggregateQuery } from '@kbn/es-query'; -import { CoreStart } from '@kbn/core/public'; +import { CoreStart, DocLinksStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; @@ -84,6 +84,7 @@ export interface IUnifiedSearchPluginServices extends Partial { application: CoreStart['application']; http: CoreStart['http']; storage: IStorageWrapper; + docLinks: DocLinksStart; data: DataPublicPluginStart; usageCollection?: UsageCollectionStart; } diff --git a/src/plugins/vis_default_editor/public/components/controls/filter.tsx b/src/plugins/vis_default_editor/public/components/controls/filter.tsx index 95740cbaa9624..e1393e71b808d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filter.tsx @@ -11,11 +11,12 @@ import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@el import { i18n } from '@kbn/i18n'; import type { Query } from '@kbn/es-query'; -import { IAggConfig, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { IAggConfig } from '@kbn/data-plugin/public'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { VisDefaultEditorKibanaServices } from '../../types'; interface FilterRowProps { id: string; arrayIndex: number; @@ -41,7 +42,19 @@ function FilterRow({ onChangeValue, onRemoveFilter, }: FilterRowProps) { - const { services } = useKibana<{ data: DataPublicPluginStart; appName: string }>(); + const { services } = useKibana(); + const { + data, + unifiedSearch, + usageCollection, + storage, + notifications, + http, + docLinks, + uiSettings, + appName, + } = services; + const [showCustomLabel, setShowCustomLabel] = useState(false); const filterLabel = i18n.translate('visDefaultEditor.controls.filters.filterLabel', { defaultMessage: 'Filter {index}', @@ -53,7 +66,7 @@ function FilterRow({ const onBlur = () => { if (value.query.length > 0) { // Store filter to the query log so that it is available in autocomplete. - services.data.query.addToQueryLog(services.appName, value); + data.query.addToQueryLog(appName, value); } }; @@ -103,6 +116,17 @@ function FilterRow({ bubbleSubmitEvent={true} languageSwitcherPopoverAnchorPosition="leftDown" size="s" + deps={{ + data, + unifiedSearch, + usageCollection, + storage, + notifications, + http, + docLinks, + uiSettings, + }} + appName={appName} />
{showCustomLabel ? ( diff --git a/src/plugins/vis_default_editor/public/types.ts b/src/plugins/vis_default_editor/public/types.ts index 68375151e5068..3e3d0fbed29f1 100644 --- a/src/plugins/vis_default_editor/public/types.ts +++ b/src/plugins/vis_default_editor/public/types.ts @@ -6,8 +6,22 @@ * Side Public License, v 1. */ -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { CoreStart, DocLinksStart } from '@kbn/core/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; export interface VisDefaultEditorKibanaServices { data: DataPublicPluginStart; + appName: string; + unifiedSearch: { + autocomplete: AutocompleteStart; + }; + usageCollection?: UsageCollectionStart; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; } diff --git a/src/plugins/vis_types/timeseries/kibana.json b/src/plugins/vis_types/timeseries/kibana.json index 9194743d3af6b..049bd6beffd6f 100644 --- a/src/plugins/vis_types/timeseries/kibana.json +++ b/src/plugins/vis_types/timeseries/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "inspector", "dataViews", "fieldFormats", "usageCollection"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "inspector", "dataViews", "fieldFormats", "usageCollection", "unifiedSearch"], "optionalPlugins": ["home"], "requiredBundles": ["unifiedSearch", "kibanaUtils", "kibanaReact", "fieldFormats"], "owner": { diff --git a/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx b/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx index f83180dcea225..05330329105de 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx @@ -6,14 +6,15 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { QueryStringInput, QueryStringInputProps } from '@kbn/unified-search-plugin/public'; -import { CoreStartContext } from '../contexts/query_input_bar_context'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IndexPatternValue } from '../../../common/types'; import { getDataViewsStart } from '../../services'; import { fetchIndexPattern, isStringTypeIndexPattern } from '../../../common/index_patterns_utils'; +import { TimeseriesVisDependencies } from '../../plugin'; type QueryBarWrapperProps = Pick & { indexPatterns: IndexPatternValue[]; @@ -30,7 +31,18 @@ export function QueryBarWrapper({ const dataViews = getDataViewsStart(); const [indexes, setIndexes] = useState([]); - const coreStartContext = useContext(CoreStartContext); + const kibana = useKibana(); + const { + appName, + unifiedSearch, + storage, + data, + notifications, + http, + docLinks, + uiSettings, + usageCollection, + } = kibana.services; useEffect(() => { async function fetchIndexes() { @@ -63,11 +75,21 @@ export function QueryBarWrapper({ return ( ); diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx index 587c665c89041..10138f180ed28 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx @@ -29,7 +29,7 @@ import { extractIndexPatternValues } from '../../../common/index_patterns_utils' import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/enums'; import { VisPicker } from './vis_picker'; import { fetchFields, VisFields } from '../lib/fetch_fields'; -import { getDataStart, getCoreStart } from '../../services'; +import { getDataStart, getCoreStart, getUnifiedSearchStart } from '../../services'; import type { TimeseriesVisParams } from '../../types'; import { UseIndexPatternModeCallout } from './use_index_patter_mode_callout'; @@ -189,6 +189,7 @@ export class VisEditor extends Component(null); +export const CoreStartContext = React.createContext({} as ICoreStartContext); export const CoreStartContextProvider = CoreStartContext.Provider; diff --git a/src/plugins/vis_types/timeseries/public/plugin.ts b/src/plugins/vis_types/timeseries/public/plugin.ts index 8ca5a916a7ce3..6054a0dcd3d3b 100644 --- a/src/plugins/vis_types/timeseries/public/plugin.ts +++ b/src/plugins/vis_types/timeseries/public/plugin.ts @@ -9,11 +9,17 @@ import type { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { IUiSettingsClient } from '@kbn/core/public'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { EditorController, TSVB_EDITOR_NAME } from './application/editor_controller'; @@ -28,6 +34,7 @@ import { setDataViewsStart, setCharts, setUsageCollectionStart, + setUnifiedSearchStart, } from './services'; import { getTimeseriesVisRenderer } from './timeseries_vis_renderer'; @@ -44,6 +51,22 @@ export interface MetricsPluginStartDependencies { dataViews: DataViewsPublicPluginStart; charts: ChartsPluginStart; usageCollection: UsageCollectionStart; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +/** @internal */ +export interface TimeseriesVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; + theme: ThemeServiceStart; + appName: string; + unifiedSearch: UnifiedSearchPublicPluginStart; + notifications: CoreStart['notifications']; + storage: IStorageWrapper; + data: DataPublicPluginStart; + usageCollection?: UsageCollectionStart; + docLinks: DocLinksStart; } /** @internal */ @@ -69,12 +92,20 @@ export class MetricsPlugin implements Plugin { public start( core: CoreStart, - { data, charts, dataViews, usageCollection, fieldFormats }: MetricsPluginStartDependencies + { + data, + charts, + dataViews, + usageCollection, + fieldFormats, + unifiedSearch, + }: MetricsPluginStartDependencies ) { setCharts(charts); setI18n(core.i18n); setFieldFormats(fieldFormats); setDataStart(data); + setUnifiedSearchStart(unifiedSearch); setDataViewsStart(dataViews); setCoreStart(core); setUsageCollectionStart(usageCollection); diff --git a/src/plugins/vis_types/timeseries/public/services.ts b/src/plugins/vis_types/timeseries/public/services.ts index c68971e24822b..d7e5777361b83 100644 --- a/src/plugins/vis_types/timeseries/public/services.ts +++ b/src/plugins/vis_types/timeseries/public/services.ts @@ -13,6 +13,7 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -22,6 +23,8 @@ export const [getFieldFormats, setFieldFormats] = export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); export const [getDataStart, setDataStart] = createGetterSetter('DataStart'); +export const [getUnifiedSearchStart, setUnifiedSearchStart] = + createGetterSetter('unifiedSearchStart'); export const [getDataViewsStart, setDataViewsStart] = createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts index 360256ccb628b..9854472c1dc3f 100644 --- a/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts @@ -8,6 +8,8 @@ import _ from 'lodash'; +import { validateField } from '../../common/fields_utils'; +import { TimeFieldNotSpecifiedError } from '../../common/errors'; import { Framework } from '../plugin'; import type { TimeseriesVisData, FetchedIndexPattern, Series } from '../../common/types'; import { PANEL_TYPES } from '../../common/enums'; @@ -20,7 +22,7 @@ import { getSeriesData } from './vis_data/get_series_data'; import { getTableData } from './vis_data/get_table_data'; import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings'; import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher'; -import { getIntervalAndTimefield } from './vis_data/get_interval_and_timefield'; +import { getInterval } from './vis_data/get_interval'; import { UI_SETTINGS } from '../../common/constants'; export async function getVisData( @@ -60,16 +62,40 @@ export async function getVisData( const maxBuckets = await uiSettings.get(UI_SETTINGS.MAX_BUCKETS_SETTING); const { min, max } = request.body.timerange; - return getIntervalAndTimefield( - panel, - index, - { - min, - max, - maxBuckets, - }, - series - ); + let timeField = + (series?.override_index_pattern ? series.series_time_field : panel.time_field) || + index.indexPattern?.timeFieldName; + + /** This code is historically in TSVB and for backward compatibility + * we cannot remove it while we support String Indexes. + * Case: only for String Indexes mode + if user doesn't provide timeField + * we should use @timestamp as default timeField **/ + if (!panel.use_kibana_indexes && !timeField) { + timeField = '@timestamp'; + } + + if (panel.use_kibana_indexes) { + if (timeField) { + validateField(timeField, index); + } else { + throw new TimeFieldNotSpecifiedError(); + } + } + + return { + timeField, + ...getInterval( + timeField!, + panel, + index, + { + min, + max, + maxBuckets, + }, + series + ), + }; }, }; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts index 815598007030d..f86ec121683a9 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; +import { getInterval } from '../get_interval'; +import { UI_SETTINGS } from '../../../../common/constants'; import type { Annotation, Panel } from '../../../../common/types'; import { buildAnnotationRequest } from './build_request_body'; import type { @@ -31,7 +33,6 @@ export async function getAnnotationRequestParams( capabilities, uiSettings, cachedIndexPatternFetcher, - buildSeriesMetaParams, }: AnnotationServices ): Promise { const annotationIndex = await cachedIndexPatternFetcher(annotation.index_pattern); @@ -44,7 +45,21 @@ export async function getAnnotationRequestParams( annotationIndex, capabilities, uiSettings, - getMetaParams: () => buildSeriesMetaParams(annotationIndex, Boolean(panel.use_kibana_indexes)), + getMetaParams: async () => { + const maxBuckets = await uiSettings.get(UI_SETTINGS.MAX_BUCKETS_SETTING); + const { min, max } = req.body.timerange; + const timeField = + annotation.time_field ?? annotationIndex.indexPattern?.timeFieldName ?? panel.time_field; + + return { + timeField, + ...getInterval(timeField!, panel, annotationIndex, { + min, + max, + maxBuckets, + }), + }; + }, }); return { diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts similarity index 69% rename from src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts rename to src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts index 45e0b410b6a6b..bcd3ea99e4f21 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { getIntervalAndTimefield } from './get_interval_and_timefield'; +import { getInterval } from './get_interval'; import { FetchedIndexPattern, Panel, Series } from '../../../common/types'; -describe('getIntervalAndTimefield(panel, series)', () => { +describe('getInterval', () => { const index: FetchedIndexPattern = {} as FetchedIndexPattern; const params = { min: '2017-01-01T00:00:00Z', @@ -17,17 +17,16 @@ describe('getIntervalAndTimefield(panel, series)', () => { maxBuckets: 1000, }; - test('returns the panel interval and timefield', () => { + test('returns the panel interval', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = {} as Series; - expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ - timeField: '@timestamp', + expect(getInterval('@timestamp', panel, index, params, series)).toEqual({ interval: 'auto', }); }); - test('returns the series interval and timefield', () => { + test('returns the series interval', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = { override_index_pattern: true, @@ -35,8 +34,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { series_time_field: 'time', } as unknown as Series; - expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ - timeField: 'time', + expect(getInterval('@timestamp', panel, index, params, series)).toEqual({ interval: '1m', }); }); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts similarity index 63% rename from src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts rename to src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts index af6eb44affabc..593eb30b7a9aa 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts @@ -5,11 +5,10 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import moment from 'moment'; import { AUTO_INTERVAL } from '../../../common/constants'; -import { validateField } from '../../../common/fields_utils'; import { validateInterval } from '../../../common/validate_interval'; -import { TimeFieldNotSpecifiedError } from '../../../common/errors'; import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; @@ -19,29 +18,13 @@ interface IntervalParams { maxBuckets: number; } -export function getIntervalAndTimefield( +export function getInterval( + timeField: string, panel: Panel, index: FetchedIndexPattern, { min, max, maxBuckets }: IntervalParams, series?: Series ) { - let timeField = - (series?.override_index_pattern ? series.series_time_field : panel.time_field) || - index.indexPattern?.timeFieldName; - - // should use @timestamp as default timeField for es indeces if user doesn't provide timeField - if (!panel.use_kibana_indexes && !timeField) { - timeField = '@timestamp'; - } - - if (panel.use_kibana_indexes) { - if (timeField) { - validateField(timeField, index); - } else { - throw new TimeFieldNotSpecifiedError(); - } - } - let interval = panel.interval; let maxBars = panel.max_bars; @@ -61,7 +44,6 @@ export function getIntervalAndTimefield( return { maxBars, - timeField, interval: interval || AUTO_INTERVAL, }; } diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index ece2b2d316eda..debe2b5cbdf19 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -8,7 +8,7 @@ import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities'; import { dateHistogram } from './date_histogram'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; +import { getInterval } from '../../get_interval'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; describe('dateHistogram(req, panel, series)', () => { @@ -46,8 +46,10 @@ describe('dateHistogram(req, panel, series)', () => { uiSettings = { get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50), }; - buildSeriesMetaParams = jest.fn(async () => { - return getIntervalAndTimefield( + buildSeriesMetaParams = jest.fn(async () => ({ + timeField: '@timestamp', + ...getInterval( + '@timestamp', panel, indexPattern, { @@ -56,8 +58,8 @@ describe('dateHistogram(req, panel, series)', () => { maxBuckets: 1000, }, series - ); - }); + ), + })); }); test('calls next when finished', async () => { @@ -163,7 +165,7 @@ describe('dateHistogram(req, panel, series)', () => { test('returns valid date histogram with overridden index pattern', async () => { series.override_index_pattern = 1; series.series_index_pattern = '*'; - series.series_time_field = 'timestamp'; + series.series_time_field = '@timestamp'; series.series_interval = '20s'; const next = (doc) => doc; const doc = await dateHistogram( @@ -183,7 +185,7 @@ describe('dateHistogram(req, panel, series)', () => { aggs: { timeseries: { date_histogram: { - field: 'timestamp', + field: '@timestamp', fixed_interval: '20s', min_doc_count: 0, time_zone: 'UTC', @@ -196,7 +198,7 @@ describe('dateHistogram(req, panel, series)', () => { }, meta: { intervalString: '20s', - timeField: 'timestamp', + timeField: '@timestamp', seriesId: 'test', panelId: 'panelId', }, diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index 7a36f94061159..fc35feb51039c 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -17,7 +17,8 @@ "screenshotMode", "presentationUtil", "dataViews", - "dataViewEditor" + "dataViewEditor", + "unifiedSearch" ], "optionalPlugins": ["home", "share", "spaces", "savedObjectsTaggingOss"], "requiredBundles": ["kibanaUtils", "savedSearch", "kibanaReact", "charts"], diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index 49a083647fbf8..e6d0e001f4572 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -23,6 +23,7 @@ import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public import { savedObjectTaggingOssPluginMock } from '@kbn/saved-objects-tagging-oss-plugin/public/mocks'; import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { VisualizationsPlugin } from './plugin'; import { Schemas } from './vis_types'; import { Schema, VisualizationsSetup, VisualizationsStart } from '.'; @@ -75,6 +76,7 @@ const createInstance = async () => { urlForwarding: urlForwardingPluginMock.createStartContract(), screenshotMode: screenshotModePluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), }); return { diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index e98ba20fe3056..50245642e7fed 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -25,6 +25,8 @@ import { withNotifyOnErrors, } from '@kbn/kibana-utils-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + import type { PluginInitializerContext, CoreSetup, @@ -135,6 +137,7 @@ export interface VisualizationsStartDeps { urlForwarding: UrlForwardingStart; screenshotMode: ScreenshotModePluginStart; fieldFormats: FieldFormatsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } /** @@ -287,6 +290,7 @@ export class VisualizationsPlugin getKibanaVersion: () => this.initializerContext.env.packageInfo.version, spaces: pluginsStart.spaces, visEditorsRegistry, + unifiedSearch: pluginsStart.unifiedSearch, }; params.element.classList.add('visAppWrapper'); diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts index 88672430c2550..8fe5ad4a60775 100644 --- a/src/plugins/visualizations/public/visualize_app/types.ts +++ b/src/plugins/visualizations/public/visualize_app/types.ts @@ -10,6 +10,8 @@ import type { EventEmitter } from 'events'; import type { History } from 'history'; import type { SerializableRecord } from '@kbn/utility-types'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + import type { CoreStart, PluginInitializerContext, @@ -110,6 +112,7 @@ export interface VisualizeServices extends CoreStart { spaces?: SpacesPluginStart; theme: ThemeServiceStart; visEditorsRegistry: VisEditorsRegistry; + unifiedSearch: UnifiedSearchPublicPluginStart; } export interface VisInstance { @@ -143,6 +146,7 @@ export interface EditorRenderProps { query?: Query; savedSearch?: SavedSearch; uiState: PersistedState; + unifiedSearch: UnifiedSearchPublicPluginStart; /** * Flag to determine if visualiztion is linked to the saved search */ diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts index 4f7245ed436d7..1492d628d5425 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts @@ -54,6 +54,7 @@ export const useEditorUpdates = ( query: queryString.getQuery() as Query, linked: !!vis.data.savedSearchId, savedSearch, + unifiedSearch: services.unifiedSearch, }); } else { embeddableHandler.updateInput({ diff --git a/versions.json b/versions.json index 5b70f97320e23..a39a1412c46f6 100644 --- a/versions.json +++ b/versions.json @@ -2,11 +2,17 @@ "notice": "This file is not maintained outside of the main branch and should only be used for tooling.", "versions": [ { - "version": "8.5.0", + "version": "8.6.0", "branch": "main", "currentMajor": true, "currentMinor": true }, + { + "version": "8.5.0", + "branch": "8.5", + "currentMajor": true, + "previousMinor": true + }, { "version": "8.4.3", "branch": "8.4", diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8c1b4ce3daa86..83466ba749605 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -18,6 +18,7 @@ "xpack.endpoint": "plugins/endpoint", "xpack.enterpriseSearch": "plugins/enterprise_search", "xpack.features": "plugins/features", + "xpack.files": "plugins/files", "xpack.dataVisualizer": "plugins/data_visualizer", "xpack.fileUpload": "plugins/file_upload", "xpack.globalSearch": ["plugins/global_search"], @@ -38,10 +39,7 @@ "xpack.logstash": ["plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps"], - "xpack.aiops": [ - "packages/ml/aiops_components", - "plugins/aiops" - ], + "xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"], "xpack.ml": ["plugins/ml"], "xpack.monitoring": ["plugins/monitoring"], "xpack.osquery": ["plugins/osquery"], diff --git a/x-pack/package.json b/x-pack/package.json index 2433a70c26123..fe6050dd8f95d 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -1,6 +1,6 @@ { "name": "x-pack", - "version": "8.5.0", + "version": "8.6.0", "author": "Elastic", "private": true, "license": "Elastic-License", diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx index ad16bd7302220..9b62ae8e6da89 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import React from 'react'; import { EuiButton, EuiFlexGroup, @@ -14,7 +15,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import React from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ApmPluginStartDeps } from '../../../../plugin'; import { TraceSearchQuery, TraceSearchType, @@ -56,7 +58,12 @@ export function TraceSearchBox({ error, loading, }: Props) { - const { unifiedSearch } = useApmPluginContext(); + const { unifiedSearch, core, data } = useApmPluginContext(); + const { notifications, http, docLinks, uiSettings } = core; + const { + services: { storage }, + } = useKibana(); + const { dataView } = useApmDataView(); return ( @@ -133,6 +140,21 @@ export function TraceSearchBox({ query: String(e.query ?? ''), }); }} + appName={i18n.translate( + 'xpack.apm.traceExplorer.appName', + { + defaultMessage: 'APM', + } + )} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + }} /> )} diff --git a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index a8f5ab1b149fb..dc6a035e15775 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -13,6 +13,7 @@ import { ObservabilityPublicStart } from '@kbn/observability-plugin/public'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { ApmPluginSetupDeps } from '../../plugin'; import { ConfigSchema } from '../..'; @@ -25,6 +26,7 @@ export interface ApmPluginContextValue { observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; observability: ObservabilityPublicStart; dataViews: DataViewsPublicPluginStart; + data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; } diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 7c9050bd3804e..0ae44172f7ebb 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -29,6 +29,7 @@ import type { PluginSetupContract as AlertingPluginPublicSetup, PluginStartContract as AlertingPluginPublicStart, } from '@kbn/alerting-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { FeaturesPluginSetup } from '@kbn/features-plugin/public'; import type { FleetStart } from '@kbn/fleet-plugin/public'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; @@ -96,6 +97,7 @@ export interface ApmPluginStartDeps { infra?: InfraClientStartExports; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + storage: IStorageWrapper; } const servicesTitle = i18n.translate('xpack.apm.navigation.servicesTitle', { diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 7f2f7d208d896..6071c455ad84e 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -31,9 +31,6 @@ const configSchema = schema.object({ transactionGroupBucketSize: schema.number({ defaultValue: 1000 }), maxTraceItems: schema.number({ defaultValue: 1000 }), }), - searchAggregatedServiceMetrics: schema.boolean({ - defaultValue: false, - }), searchAggregatedTransactions: schema.oneOf( [ schema.literal(SearchAggregatedTransactionSetting.auto), diff --git a/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts index c668eda669e29..459eddfbdcc1f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts @@ -11,11 +11,13 @@ import { APMConfig } from '../..'; export async function getServiceInventorySearchSource({ config, + serviceMetricsEnabled, apmEventClient, start, end, kuery, }: { + serviceMetricsEnabled: boolean; config: APMConfig; apmEventClient: APMEventClient; start: number; @@ -26,7 +28,6 @@ export async function getServiceInventorySearchSource({ searchAggregatedServiceMetrics: boolean; }> { const commonProps = { - config, apmEventClient, kuery, start, @@ -34,8 +35,11 @@ export async function getServiceInventorySearchSource({ }; const [searchAggregatedTransactions, searchAggregatedServiceMetrics] = await Promise.all([ - getSearchAggregatedTransactions(commonProps), - getSearchAggregatedServiceMetrics(commonProps), + getSearchAggregatedTransactions({ ...commonProps, config }), + getSearchAggregatedServiceMetrics({ + ...commonProps, + serviceMetricsEnabled, + }), ]); return { diff --git a/x-pack/plugins/apm/server/lib/helpers/service_metrics/get_has_aggregated_service_metrics.test.ts b/x-pack/plugins/apm/server/lib/helpers/service_metrics/get_has_aggregated_service_metrics.test.ts deleted file mode 100644 index d78190d56e20a..0000000000000 --- a/x-pack/plugins/apm/server/lib/helpers/service_metrics/get_has_aggregated_service_metrics.test.ts +++ /dev/null @@ -1,124 +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 { getSearchAggregatedServiceMetrics } from '.'; -import { - SearchParamsMock, - inspectSearchParams, -} from '../../../utils/test_helpers'; -import { Setup } from '../setup_request'; - -const mockResponseWithServiceMetricsHits = { - took: 398, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 1, - relation: 'gte' as const, - }, - hits: [], - }, -}; - -const mockResponseWithServiceMetricsNoHits = { - took: 398, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 0, - relation: 'gte' as const, - }, - hits: [], - }, -}; - -describe('get default configuration for aggregated service metrics', () => { - it('should be false by default', async () => { - const mockSetup = { - apmEventClient: { search: () => Promise.resolve(response) }, - config: {}, - } as unknown as Setup; - - const response = await getSearchAggregatedServiceMetrics({ - apmEventClient: mockSetup.apmEventClient, - config: mockSetup.config, - kuery: '', - }); - expect(response).toBeFalsy(); - }); -}); - -describe('get has aggregated', () => { - it('should be false when xpack.apm.searchAggregatedServiceMetrics=false ', async () => { - const mockSetup = { - apmEventClient: { search: () => Promise.resolve(response) }, - config: { 'xpack.apm.searchAggregatedServiceMetrics': false }, - } as unknown as Setup; - - const response = await getSearchAggregatedServiceMetrics({ - apmEventClient: mockSetup.apmEventClient, - config: mockSetup.config, - kuery: '', - }); - expect(response).toBeFalsy(); - }); - - describe('with xpack.apm.searchAggregatedServiceMetrics=true', () => { - let mock: SearchParamsMock; - - const config = { - searchAggregatedServiceMetrics: true, - }; - - afterEach(() => { - mock.teardown(); - }); - it('should be true when service metrics data are found', async () => { - mock = await inspectSearchParams( - (setup) => - getSearchAggregatedServiceMetrics({ - apmEventClient: setup.apmEventClient, - config: setup.config, - kuery: '', - }), - { - config, - mockResponse: () => mockResponseWithServiceMetricsHits, - } - ); - expect(mock.response).toBeTruthy(); - }); - - it('should be false when service metrics data are not found', async () => { - mock = await inspectSearchParams( - (setup) => - getSearchAggregatedServiceMetrics({ - apmEventClient: setup.apmEventClient, - config: setup.config, - kuery: '', - }), - { - config, - mockResponse: () => mockResponseWithServiceMetricsNoHits, - } - ); - expect(mock.response).toBeFalsy(); - }); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts index fd449734e9772..175267bdf5c2b 100644 --- a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts @@ -8,23 +8,22 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { METRICSET_NAME } from '../../../../common/elasticsearch_fieldnames'; -import { APMConfig } from '../../..'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; export async function getSearchAggregatedServiceMetrics({ - config, + serviceMetricsEnabled, start, end, apmEventClient, kuery, }: { - config: APMConfig; + serviceMetricsEnabled: boolean; start?: number; end?: number; apmEventClient: APMEventClient; kuery: string; }): Promise { - if (config.searchAggregatedServiceMetrics) { + if (serviceMetricsEnabled) { return getHasAggregatedServicesMetrics({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 0c77c5b0be86f..6b73d72367d0f 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { isoToEpochRt, jsonRt, toNumberRt } from '@kbn/io-ts-utils'; +import { enableServiceMetrics } from '@kbn/observability-plugin/common'; import * as t from 'io-ts'; import { uniq, mergeWith } from 'lodash'; import { @@ -122,6 +123,7 @@ const servicesRoute = createApmServerRoute({ probability, } = params.query; const savedObjectsClient = (await context.core).savedObjects.client; + const coreContext = await resources.context.core; const [setup, serviceGroup, randomSampler] = await Promise.all([ setupRequest(resources), @@ -132,8 +134,13 @@ const servicesRoute = createApmServerRoute({ ]); const { apmEventClient, config } = setup; + + const serviceMetricsEnabled = + await coreContext.uiSettings.client.get(enableServiceMetrics); + const { searchAggregatedTransactions, searchAggregatedServiceMetrics } = await getServiceInventorySearchSource({ + serviceMetricsEnabled, config, apmEventClient, kuery, @@ -208,6 +215,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ request, plugins: { security }, } = resources; + const coreContext = await resources.context.core; const { environment, kuery, offset, start, end, probability } = params.query; @@ -220,8 +228,13 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ ]); const { apmEventClient, config } = setup; + + const serviceMetricsEnabled = + await coreContext.uiSettings.client.get(enableServiceMetrics); + const { searchAggregatedTransactions, searchAggregatedServiceMetrics } = await getServiceInventorySearchSource({ + serviceMetricsEnabled, config, apmEventClient, kuery, diff --git a/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx b/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx index 4ec853a0f9a24..46138fb354604 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx @@ -9,7 +9,12 @@ import React from 'react'; import { configure, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import RecentCases, { RecentCasesProps } from '.'; -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; +import { + AppMockRenderer, + createAppMockRenderer, + noCasesCapabilities, + TestProviders, +} from '../../common/mock'; import { useGetCasesMockState } from '../../containers/mock'; import { useCurrentUser } from '../../common/lib/kibana/hooks'; import { useGetCases } from '../../containers/use_get_cases'; @@ -82,7 +87,7 @@ describe('RecentCases', () => { ); expect(useGetCasesMock).toHaveBeenCalledWith({ - filterOptions: { reporters: [] }, + filterOptions: { reporters: [], owner: ['securitySolution'] }, queryParams: { perPage: 2 }, }); }); @@ -95,7 +100,7 @@ describe('RecentCases', () => { ); expect(useGetCasesMock).toHaveBeenCalledWith({ - filterOptions: { reporters: [] }, + filterOptions: { reporters: [], owner: ['securitySolution'] }, queryParams: { perPage: 10 }, }); @@ -115,6 +120,7 @@ describe('RecentCases', () => { username: 'damaged_raccoon', }, ], + owner: ['securitySolution'], }, queryParams: { perPage: 10 }, }); @@ -126,6 +132,7 @@ describe('RecentCases', () => { expect(useGetCasesMock).toHaveBeenLastCalledWith({ filterOptions: { reporters: [], + owner: ['securitySolution'], }, queryParams: { perPage: 10 }, }); @@ -141,7 +148,7 @@ describe('RecentCases', () => { ); expect(useGetCasesMock).toHaveBeenCalledWith({ - filterOptions: { reporters: [] }, + filterOptions: { reporters: [], owner: ['securitySolution'] }, queryParams: { perPage: 10 }, }); @@ -160,8 +167,29 @@ describe('RecentCases', () => { username: 'elastic', }, ], + owner: ['securitySolution'], }, queryParams: { perPage: 10 }, }); }); + + it('sets all available solutions correctly', () => { + appMockRender = createAppMockRenderer({ owner: [] }); + /** + * We set securitySolutionCases capability to not have + * any access to cases. This tests that we get the owners + * that have at least read access. + */ + appMockRender.coreStart.application.capabilities = { + ...appMockRender.coreStart.application.capabilities, + securitySolutionCases: noCasesCapabilities(), + }; + + appMockRender.render(); + + expect(useGetCasesMock).toHaveBeenCalledWith({ + filterOptions: { reporters: [], owner: ['cases'] }, + queryParams: { perPage: 2 }, + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx index 44eee0c0b23f8..190972eeb9327 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx @@ -18,6 +18,8 @@ import { MarkdownRenderer } from '../markdown_editor'; import { FilterOptions } from '../../containers/types'; import { TruncatedText } from '../truncated_text'; import { initialData as initialGetCasesData, useGetCases } from '../../containers/use_get_cases'; +import { useAvailableCasesOwners } from '../app/use_available_owners'; +import { useCasesContext } from '../cases_context/use_cases_context'; const MarkdownContainer = styled.div` max-height: 150px; @@ -31,9 +33,13 @@ export interface RecentCasesProps { } export const RecentCasesComp = ({ filterOptions, maxCasesToShow }: RecentCasesProps) => { + const { owner } = useCasesContext(); + const availableSolutions = useAvailableCasesOwners(['read']); + const hasOwner = !!owner.length; + const { data = initialGetCasesData, isLoading: isLoadingCases } = useGetCases({ queryParams: { perPage: maxCasesToShow }, - filterOptions, + filterOptions: { ...filterOptions, owner: hasOwner ? owner : availableSolutions }, }); return isLoadingCases ? ( diff --git a/x-pack/plugins/cases/server/telemetry/constants.ts b/x-pack/plugins/cases/server/telemetry/constants.ts new file mode 100644 index 0000000000000..705321e3f1fa0 --- /dev/null +++ b/x-pack/plugins/cases/server/telemetry/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common'; + +/** + * This should only be used within telemetry + */ +export const OWNERS = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER] as const; diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts index 71c484071017c..2a6aaddf8f5db 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts @@ -7,6 +7,7 @@ import { SavedObjectsFindResponse } from '@kbn/core/server'; import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { CaseAggregationResult } from '../types'; import { getCasesTelemetryData } from './cases'; describe('getCasesTelemetryData', () => { @@ -50,13 +51,33 @@ describe('getCasesTelemetryData', () => { ], }; - mockFind({ + const assignees = { + assigneeFilters: { + buckets: { + atLeastOne: { + doc_count: 0, + }, + zero: { + doc_count: 100, + }, + }, + }, + totalAssignees: { value: 5 }, + }; + + const solutionValues = { + counts, + ...assignees, + }; + + const caseAggsResult: CaseAggregationResult = { users: { value: 1 }, tags: { value: 2 }, + ...assignees, counts, - securitySolution: { counts }, - observability: { counts }, - cases: { counts }, + securitySolution: { ...solutionValues }, + observability: { ...solutionValues }, + cases: { ...solutionValues }, syncAlerts: { buckets: [ { @@ -93,7 +114,9 @@ describe('getCasesTelemetryData', () => { }, ], }, - }); + }; + + mockFind(caseAggsResult); mockFind({ participants: { value: 2 } }); mockFind({ references: { referenceType: { referenceAgg: { value: 3 } } } }); mockFind({ references: { referenceType: { referenceAgg: { value: 4 } } } }); @@ -139,20 +162,40 @@ describe('getCasesTelemetryData', () => { totalUsers: 1, totalWithAlerts: 3, totalWithConnectors: 4, + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, }, main: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, monthly: 1, }, obs: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, monthly: 1, }, sec: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, @@ -166,145 +209,263 @@ describe('getCasesTelemetryData', () => { await getCasesTelemetryData({ savedObjectsClient, logger }); - expect(savedObjectsClient.find.mock.calls[0][0]).toEqual({ - aggs: { - cases: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', + expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, }, - { - from: 'now-1M', - to: 'now', + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, }, - ], + }, }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'cases', - }, - }, - }, - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', + "cases": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + }, + }, }, - { - from: 'now-1w', - to: 'now', + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, }, - { - from: 'now-1M', - to: 'now', + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, }, - ], - }, - }, - observability: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', - }, - { - from: 'now-1M', - to: 'now', - }, - ], + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "cases", }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'observability', + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], }, }, - }, - securitySolution: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', + "observability": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, }, - { - from: 'now-1M', - to: 'now', + }, + }, + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, + }, + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "observability", + }, + }, + }, + "securitySolution": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, }, - ], + }, + }, + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, + }, + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "securitySolution", }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'securitySolution', + "status": Object { + "terms": Object { + "field": "cases.attributes.status", }, }, - }, - status: { - terms: { - field: 'cases.attributes.status', + "syncAlerts": Object { + "terms": Object { + "field": "cases.attributes.settings.syncAlerts", + }, }, - }, - syncAlerts: { - terms: { - field: 'cases.attributes.settings.syncAlerts', + "tags": Object { + "cardinality": Object { + "field": "cases.attributes.tags", + }, }, - }, - tags: { - cardinality: { - field: 'cases.attributes.tags', + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, }, - }, - totalsByOwner: { - terms: { - field: 'cases.attributes.owner', + "totalsByOwner": Object { + "terms": Object { + "field": "cases.attributes.owner", + }, }, - }, - users: { - cardinality: { - field: 'cases.attributes.created_by.username', + "users": Object { + "cardinality": Object { + "field": "cases.attributes.created_by.username", + }, }, }, - }, - page: 0, - perPage: 0, - type: 'cases', - }); + "page": 0, + "perPage": 0, + "type": "cases", + } + `); expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({ aggs: { diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index 3af4a97c07a2b..3969ed77e5a17 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -11,6 +11,7 @@ import { CASE_USER_ACTION_SAVED_OBJECT, } from '../../../common/constants'; import { ESCaseAttributes } from '../../services/cases/types'; +import { OWNERS } from '../constants'; import { CollectTelemetryDataParams, Buckets, @@ -18,6 +19,7 @@ import { Cardinality, ReferencesAggregation, LatestDates, + CaseAggregationResult, } from '../types'; import { findValueInBuckets, @@ -27,6 +29,7 @@ import { getOnlyAlertsCommentsFilter, getOnlyConnectorsFilter, getReferencesAggregationQuery, + getSolutionValues, } from './utils'; export const getLatestCasesDates = async ({ @@ -58,8 +61,7 @@ export const getCasesTelemetryData = async ({ savedObjectsClient, logger, }: CollectTelemetryDataParams): Promise => { - const owners = ['observability', 'securitySolution', 'cases'] as const; - const byOwnerAggregationQuery = owners.reduce( + const byOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, [owner]: { @@ -68,28 +70,23 @@ export const getCasesTelemetryData = async ({ [`${CASE_SAVED_OBJECT}.attributes.owner`]: owner, }, }, - aggs: getCountsAggregationQuery(CASE_SAVED_OBJECT), + aggs: { + ...getCountsAggregationQuery(CASE_SAVED_OBJECT), + ...getAssigneesAggregations(), + }, }, }), {} ); - const casesRes = await savedObjectsClient.find< - unknown, - Record & { - counts: Buckets; - syncAlerts: Buckets; - status: Buckets; - users: Cardinality; - tags: Cardinality; - } - >({ + const casesRes = await savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_SAVED_OBJECT, aggs: { ...byOwnerAggregationQuery, ...getCountsAggregationQuery(CASE_SAVED_OBJECT), + ...getAssigneesAggregations(), totalsByOwner: { terms: { field: `${CASE_SAVED_OBJECT}.attributes.owner` }, }, @@ -116,7 +113,7 @@ export const getCasesTelemetryData = async ({ const commentsRes = await savedObjectsClient.find< unknown, - Record & { + Record & { participants: Cardinality; } & ReferencesAggregation >({ @@ -164,16 +161,7 @@ export const getCasesTelemetryData = async ({ const aggregationsBuckets = getAggregationsBuckets({ aggs: casesRes.aggregations, - keys: [ - 'counts', - 'observability.counts', - 'securitySolution.counts', - 'cases.counts', - 'syncAlerts', - 'status', - 'totalsByOwner', - 'users', - ], + keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'], }); return { @@ -195,18 +183,47 @@ export const getCasesTelemetryData = async ({ totalWithConnectors: totalConnectorsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0, latestDates, + assignees: { + total: casesRes.aggregations?.totalAssignees.value ?? 0, + totalWithZero: casesRes.aggregations?.assigneeFilters.buckets.zero.doc_count ?? 0, + totalWithAtLeastOne: + casesRes.aggregations?.assigneeFilters.buckets.atLeastOne.doc_count ?? 0, + }, }, - sec: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'securitySolution'), - ...getCountsFromBuckets(aggregationsBuckets['securitySolution.counts']), - }, - obs: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'observability'), - ...getCountsFromBuckets(aggregationsBuckets['observability.counts']), - }, - main: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'cases'), - ...getCountsFromBuckets(aggregationsBuckets['cases.counts']), - }, + sec: getSolutionValues(casesRes.aggregations, 'securitySolution'), + obs: getSolutionValues(casesRes.aggregations, 'observability'), + main: getSolutionValues(casesRes.aggregations, 'cases'), }; }; + +const getAssigneesAggregations = () => ({ + totalAssignees: { + value_count: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + assigneeFilters: { + filters: { + filters: { + zero: { + bool: { + must_not: { + exists: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + }, + }, + atLeastOne: { + bool: { + filter: { + exists: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + }, + }, + }, + }, + }, +}); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts index f10cae4afb611..3b1bd17b28cdf 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -6,6 +6,7 @@ */ import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { CaseAggregationResult } from '../types'; import { findValueInBuckets, getAggregationsBuckets, @@ -18,9 +19,127 @@ import { getOnlyAlertsCommentsFilter, getOnlyConnectorsFilter, getReferencesAggregationQuery, + getSolutionValues, } from './utils'; describe('utils', () => { + describe('getSolutionValues', () => { + const counts = { + buckets: [ + { doc_count: 1, key: 1 }, + { doc_count: 2, key: 2 }, + { doc_count: 3, key: 3 }, + ], + }; + + const assignees = { + assigneeFilters: { + buckets: { + atLeastOne: { + doc_count: 0, + }, + zero: { + doc_count: 100, + }, + }, + }, + totalAssignees: { value: 5 }, + }; + + const solutionValues = { + counts, + ...assignees, + }; + + const aggsResult: CaseAggregationResult = { + users: { value: 1 }, + tags: { value: 2 }, + ...assignees, + counts, + securitySolution: { ...solutionValues }, + observability: { ...solutionValues }, + cases: { ...solutionValues }, + syncAlerts: { + buckets: [ + { + key: 0, + doc_count: 1, + }, + { + key: 1, + doc_count: 1, + }, + ], + }, + status: { + buckets: [ + { + key: 'open', + doc_count: 2, + }, + ], + }, + totalsByOwner: { + buckets: [ + { + key: 'observability', + doc_count: 1, + }, + { + key: 'securitySolution', + doc_count: 1, + }, + { + key: 'cases', + doc_count: 1, + }, + ], + }, + }; + + it('constructs the solution values correctly', () => { + expect(getSolutionValues(aggsResult, 'securitySolution')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + expect(getSolutionValues(aggsResult, 'cases')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + expect(getSolutionValues(aggsResult, 'observability')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + }); + }); + describe('getCountsAggregationQuery', () => { it('returns the correct query', () => { expect(getCountsAggregationQuery('test')).toEqual({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index 70e719ef9fa31..dd542b5f65229 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -13,8 +13,15 @@ import { CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, } from '../../../common/constants'; -import { Buckets, CasesTelemetry, MaxBucketOnCaseAggregation } from '../types'; +import { + CaseAggregationResult, + Buckets, + CasesTelemetry, + MaxBucketOnCaseAggregation, + SolutionTelemetry, +} from '../types'; import { buildFilter } from '../../client/utils'; +import { OWNERS } from '../constants'; export const getCountsAggregationQuery = (savedObjectType: string) => ({ counts: { @@ -147,6 +154,26 @@ export const getBucketFromAggregation = ({ aggs?: Record; }): Buckets['buckets'] => (get(aggs, `${key}.buckets`) ?? []) as Buckets['buckets']; +export const getSolutionValues = ( + aggregations: CaseAggregationResult | undefined, + owner: typeof OWNERS[number] +): SolutionTelemetry => { + const aggregationsBuckets = getAggregationsBuckets({ + aggs: aggregations, + keys: ['totalsByOwner', 'securitySolution.counts', 'observability.counts', 'cases.counts'], + }); + + return { + total: findValueInBuckets(aggregationsBuckets.totalsByOwner, owner), + ...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]), + assignees: { + total: aggregations?.[owner].totalAssignees.value ?? 0, + totalWithZero: aggregations?.[owner].assigneeFilters.buckets.zero.doc_count ?? 0, + totalWithAtLeastOne: aggregations?.[owner].assigneeFilters.buckets.atLeastOne.doc_count ?? 0, + }, + }; +}; + export const findValueInBuckets = (buckets: Buckets['buckets'], value: string | number): number => buckets.find(({ key }) => key === value)?.doc_count ?? 0; @@ -184,6 +211,11 @@ export const getOnlyConnectorsFilter = () => export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ cases: { all: { + assignees: { + total: 0, + totalWithZero: 0, + totalWithAtLeastOne: 0, + }, total: 0, monthly: 0, weekly: 0, @@ -206,9 +238,27 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ closedAt: null, }, }, - sec: { total: 0, monthly: 0, weekly: 0, daily: 0 }, - obs: { total: 0, monthly: 0, weekly: 0, daily: 0 }, - main: { total: 0, monthly: 0, weekly: 0, daily: 0 }, + sec: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, + obs: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, + main: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, }, userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, comments: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index 5b8b75cc01833..fa04fc7c6651d 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -12,6 +12,8 @@ import { StatusSchema, LatestDatesSchema, TypeString, + SolutionTelemetrySchema, + AssigneesSchema, } from './types'; const long: TypeLong = { type: 'long' }; @@ -24,6 +26,17 @@ const countSchema: CountSchema = { daily: long, }; +const assigneesSchema: AssigneesSchema = { + total: long, + totalWithZero: long, + totalWithAtLeastOne: long, +}; + +const solutionTelemetry: SolutionTelemetrySchema = { + ...countSchema, + assignees: assigneesSchema, +}; + const statusSchema: StatusSchema = { open: long, inProgress: long, @@ -40,6 +53,7 @@ export const casesSchema: CasesTelemetrySchema = { cases: { all: { ...countSchema, + assignees: assigneesSchema, status: statusSchema, syncAlertsOn: long, syncAlertsOff: long, @@ -50,9 +64,9 @@ export const casesSchema: CasesTelemetrySchema = { totalWithConnectors: long, latestDates: latestDatesSchema, }, - sec: countSchema, - obs: countSchema, - main: countSchema, + sec: solutionTelemetry, + obs: solutionTelemetry, + main: solutionTelemetry, }, userActions: { all: { ...countSchema, maxOnACase: long } }, comments: { all: { ...countSchema, maxOnACase: long } }, diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index ec880855f3302..2c8e848b3854f 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -7,6 +7,7 @@ import { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; +import { OWNERS } from './constants'; export interface Buckets { buckets: Array<{ @@ -19,6 +20,8 @@ export interface Cardinality { value: number; } +export type ValueCount = Cardinality; + export interface MaxBucketOnCaseAggregation { references: { cases: { max: { value: number } } }; } @@ -47,6 +50,41 @@ export interface Count { daily: number; } +export interface AssigneesFilters { + buckets: { + zero: { doc_count: number }; + atLeastOne: { doc_count: number }; + }; +} + +export type CaseAggregationResult = Record< + typeof OWNERS[number], + { + counts: Buckets; + totalAssignees: ValueCount; + assigneeFilters: AssigneesFilters; + } +> & { + assigneeFilters: AssigneesFilters; + counts: Buckets; + syncAlerts: Buckets; + status: Buckets; + users: Cardinality; + tags: Cardinality; + totalAssignees: ValueCount; + totalsByOwner: Buckets; +}; + +export interface Assignees { + total: number; + totalWithZero: number; + totalWithAtLeastOne: number; +} + +export interface SolutionTelemetry extends Count { + assignees: Assignees; +} + export interface Status { open: number; inProgress: number; @@ -62,6 +100,7 @@ export interface LatestDates { export interface CasesTelemetry { cases: { all: Count & { + assignees: Assignees; status: Status; syncAlertsOn: number; syncAlertsOff: number; @@ -72,9 +111,9 @@ export interface CasesTelemetry { totalWithConnectors: number; latestDates: LatestDates; }; - sec: Count; - obs: Count; - main: Count; + sec: SolutionTelemetry; + obs: SolutionTelemetry; + main: SolutionTelemetry; }; userActions: { all: Count & { maxOnACase: number } }; comments: { all: Count & { maxOnACase: number } }; @@ -107,3 +146,5 @@ export type CountSchema = MakeSchemaFrom; export type StatusSchema = MakeSchemaFrom; export type LatestDatesSchema = MakeSchemaFrom; export type CasesTelemetrySchema = MakeSchemaFrom; +export type AssigneesSchema = MakeSchemaFrom; +export type SolutionTelemetrySchema = MakeSchemaFrom; diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 240d14c4b573c..764cb49dae8d2 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -30,6 +30,8 @@ export const BENCHMARK_SCORE_INDEX_PATTERN = 'logs-cloud_security_posture.scores export const BENCHMARK_SCORE_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.scores-default'; export const CSP_INGEST_TIMESTAMP_PIPELINE = 'cloud_security_posture_add_ingest_timestamp_pipeline'; +export const CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE = + 'cloud_security_posture_latest_index_add_ingest_timestamp_pipeline'; export const RULE_PASSED = `passed`; export const RULE_FAILED = `failed`; diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/create_indices.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/create_indices.ts index 62deda2b9ee3f..d34678acdc845 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/create_indices.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/create_indices.ts @@ -11,7 +11,6 @@ import { BENCHMARK_SCORE_INDEX_PATTERN, BENCHMARK_SCORE_INDEX_TEMPLATE_NAME, CLOUD_SECURITY_POSTURE_PACKAGE_NAME, - CSP_INGEST_TIMESTAMP_PIPELINE, FINDINGS_INDEX_NAME, LATEST_FINDINGS_INDEX_DEFAULT_NS, LATEST_FINDINGS_INDEX_PATTERN, @@ -19,11 +18,15 @@ import { } from '../../common/constants'; import { createPipelineIfNotExists } from './create_processor'; import { benchmarkScoreMapping } from './benchmark_score_mapping'; +import { latestFindingsPipelineIngestConfig, scorePipelineIngestConfig } from './ingest_pipelines'; // TODO: Add integration tests export const initializeCspIndices = async (esClient: ElasticsearchClient, logger: Logger) => { - await createPipelineIfNotExists(esClient, CSP_INGEST_TIMESTAMP_PIPELINE, logger); + await Promise.all([ + createPipelineIfNotExists(esClient, scorePipelineIngestConfig, logger), + createPipelineIfNotExists(esClient, latestFindingsPipelineIngestConfig, logger), + ]); return Promise.all([ createLatestFindingsIndex(esClient, logger), @@ -44,7 +47,7 @@ const createBenchmarkScoreIndex = async (esClient: ElasticsearchClient, logger: template: { mappings: benchmarkScoreMapping, settings: { - default_pipeline: CSP_INGEST_TIMESTAMP_PIPELINE, + default_pipeline: scorePipelineIngestConfig.id, lifecycle: { // This is the default lifecycle name, it is named on the data-stream type (e.g, logs/ metrics) name: 'logs', @@ -84,20 +87,24 @@ const createLatestFindingsIndex = async (esClient: ElasticsearchClient, logger: const { template, composed_of, _meta } = findingsIndexTemplateResponse.index_templates[0].index_template; - if (template?.settings) { - template.settings.lifecycle = { - name: '', - }; - } - // We always want to keep the index template updated await esClient.indices.putIndexTemplate({ name: LATEST_FINDINGS_INDEX_TEMPLATE_NAME, index_patterns: LATEST_FINDINGS_INDEX_PATTERN, priority: 500, + template: { + mappings: template?.mappings, + settings: { + ...template?.settings, + default_pipeline: latestFindingsPipelineIngestConfig.id, + lifecycle: { + name: '', + }, + }, + aliases: template?.aliases, + }, _meta, composed_of, - template, }); await createIndexSafe(esClient, logger, LATEST_FINDINGS_INDEX_DEFAULT_NS); diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/create_processor.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/create_processor.ts index 704765afc976c..19755bc326039 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/create_processor.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/create_processor.ts @@ -6,17 +6,20 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; /** - * @param pipelineId - the pipeline id to create. If a pipeline with the same pipelineId already exists, nothing is created or updated. - * + * @param logger - logger + * @param pipelineConf - ingest pipeline configuration + * @param esClient - the elasticsearch client * @return true if the pipeline exits or created, false otherwise. */ export const createPipelineIfNotExists = async ( esClient: ElasticsearchClient, - pipelineId: string, + pipelineConf: IngestPutPipelineRequest, logger: Logger ) => { + const pipelineId = pipelineConf.id; try { await esClient.ingest.getPipeline({ id: pipelineId }); logger.trace(`pipeline: ${pipelineId} already exists`); @@ -25,26 +28,7 @@ export const createPipelineIfNotExists = async ( const exitError = transformError(exitErr); if (exitError.statusCode === 404) { try { - await esClient.ingest.putPipeline({ - id: pipelineId, - description: 'Pipeline for adding event timestamp', - processors: [ - { - set: { - field: '@timestamp', - value: '{{_ingest.timestamp}}', - }, - }, - ], - on_failure: [ - { - set: { - field: 'error.message', - value: '{{ _ingest.on_failure_message }}', - }, - }, - ], - }); + await esClient.ingest.putPipeline(pipelineConf); logger.trace(`pipeline: ${pipelineId} was created`); return true; } catch (existError) { diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts new file mode 100644 index 0000000000000..7948fd9d90578 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + CSP_INGEST_TIMESTAMP_PIPELINE, + CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE, +} from '../../common/constants'; + +export const scorePipelineIngestConfig: IngestPutPipelineRequest = { + id: CSP_INGEST_TIMESTAMP_PIPELINE, + description: 'Pipeline for adding event timestamp', + processors: [ + { + set: { + field: '@timestamp', + value: '{{_ingest.timestamp}}', + }, + }, + ], + on_failure: [ + { + set: { + field: 'error.message', + value: '{{ _ingest.on_failure_message }}', + }, + }, + ], +}; + +export const latestFindingsPipelineIngestConfig: IngestPutPipelineRequest = { + id: CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE, + description: 'Pipeline for cloudbeat latest findings index', + processors: [ + { + set: { + field: 'event.ingested', + value: '{{_ingest.timestamp}}', + }, + }, + ], + on_failure: [ + { + set: { + field: 'error.message', + value: '{{ _ingest.on_failure_message }}', + }, + }, + ], +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx index e18a8a37cf22e..5452e26207450 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { type Filter, isFilters, isFilterPinned, Query, TimeRange } from '@kbn/es-query'; +import { type Filter, isFilterPinned, Query, TimeRange } from '@kbn/es-query'; import type { KibanaLocation } from '@kbn/share-plugin/public'; import { DashboardAppLocatorParams, cleanEmptyKeys } from '@kbn/dashboard-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; @@ -62,12 +62,11 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown isFilterPinned(f)); + params.filters = config.useCurrentFilters + ? input.filters + : input.filters?.filter((f) => isFilterPinned(f)); } const { restOfFilters: filtersFromEvent, timeRange: timeRangeFromEvent } = extractTimeRange( diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 9438ce5ef4bc1..f6b3141cf9833 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -6,8 +6,8 @@ */ export interface InferencePipeline { + isDeployed: boolean; pipelineName: string; trainedModelName: string; - isDeployed: boolean; - modelType: string; + types: string[]; } diff --git a/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts b/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts index dc960fb103ddd..d06902fe04fab 100644 --- a/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts +++ b/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts @@ -6,5 +6,4 @@ */ export const enterpriseSearchFeatureId = 'enterpriseSearch'; -export const enableIndexPipelinesTab = 'enterpriseSearch:enableIndexTransformsTab'; export const enableBehavioralAnalyticsSection = 'enterpriseSearch:enableBehavioralAnalyticsSection'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx index f329e0bf2b677..1c79cff0244e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx @@ -16,15 +16,15 @@ import { EuiBadge, EuiHealth, EuiPanel, EuiTitle } from '@elastic/eui'; import { InferencePipelineCard } from './inference_pipeline_card'; export const DEFAULT_VALUES = { + isDeployed: true, pipelineName: 'Sample Processor', trainedModelName: 'example_trained_model', - isDeployed: true, - modelType: 'pytorch', + types: ['pytorch'], }; const mockValues = { ...DEFAULT_VALUES }; -describe('InfererencePipelineCard', () => { +describe('InferencePipelineCard', () => { beforeEach(() => { jest.clearAllMocks(); setMockValues(mockValues); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx index 270e2dc5d1714..e67cbdd9005c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx @@ -37,7 +37,7 @@ export const InferencePipelineCard: React.FC = ({ pipelineName, trainedModelName, isDeployed, - modelType, + types, }) => { const { http } = useValues(HttpLogic); const { indexName } = useValues(IndexNameLogic); @@ -135,13 +135,15 @@ export const InferencePipelineCard: React.FC = ({ {deployedText}
)} - - - - {modelType} - - - + {types.map((type) => ( + + + + {type} + + + + ))} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx index 6a11c17e878aa..2bc65e041cc86 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx @@ -22,7 +22,7 @@ export const AddMLInferencePipelineButton: React.FC { const { capabilities } = useValues(KibanaLogic); - const { canUseMlInferencePipeline } = useValues(PipelinesLogic); + const { canUseMlInferencePipeline, hasIndexIngestionPipeline } = useValues(PipelinesLogic); const hasMLPermissions = capabilities?.ml?.canAccessML ?? false; if (!hasMLPermissions) { return ( @@ -36,6 +36,21 @@ export const AddMLInferencePipelineButton: React.FC ); } + if (!hasIndexIngestionPipeline) { + return ( + + + + ); + } if (!canUseMlInferencePipeline) { return ( { const { - addInferencePipelineModal: { configuration, indexName }, + addInferencePipelineModal: { configuration }, formErrors, supportedMLModels, sourceFields, @@ -78,10 +78,7 @@ export const ConfigurePipeline: React.FC = () => { 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.name.helpText', { defaultMessage: - 'Pipeline names can only contain letters, numbers, underscores, and hyphens. The pipeline name will be automatically prefixed with "ml-inference@{indexName}-".', - values: { - indexName, - }, + 'Pipeline names can only contain letters, numbers, underscores, and hyphens. The pipeline name will be automatically prefixed with "ml-inference-".', } ) } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts index 3ea6890c41932..4a9c11faa7f73 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts @@ -31,11 +31,6 @@ describe('ml inference utils', () => { ner: {}, }, }), - makeFakeModel({ - inference_config: { - classification: {}, - }, - }), makeFakeModel({ inference_config: { text_classification: {}, @@ -53,6 +48,16 @@ describe('ml inference utils', () => { }, }, }), + makeFakeModel({ + inference_config: { + question_answering: {}, + }, + }), + makeFakeModel({ + inference_config: { + fill_mask: {}, + }, + }), ]; for (const model of models) { @@ -61,7 +66,14 @@ describe('ml inference utils', () => { }); it('returns false for expected models', () => { - const models: TrainedModelConfigResponse[] = [makeFakeModel({})]; + const models: TrainedModelConfigResponse[] = [ + makeFakeModel({}), + makeFakeModel({ + inference_config: { + classification: {}, + }, + }), + ]; for (const model of models) { expect(isSupportedMLModel(model)).toBe(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts index 83cf04585b5ff..b788a522d395f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts @@ -11,10 +11,11 @@ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_ import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types'; const NLP_CONFIG_KEYS = [ + 'fill_mask', 'ner', - 'classification', 'text_classification', 'text_embedding', + 'question_answering', 'zero_shot_classification', ]; export const isSupportedMLModel = (model: TrainedModelConfigResponse): boolean => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx index 0b8d7ec671753..ab90aa1d85464 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx @@ -36,7 +36,7 @@ export const MlInferencePipelineProcessorsCard: React.FC = () => { trainedModelName={item.trainedModelName} pipelineName={item.pipelineName} isDeployed={item.isDeployed} - modelType={item.modelType} + types={item.types} /> ))} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index 47b60b7c4f4b3..7dc3a221cc57a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -30,6 +30,7 @@ const DEFAULT_VALUES = { pipelineState: DEFAULT_PIPELINE_VALUES, showModal: false, showAddMlInferencePipelineModal: false, + hasIndexIngestionPipeline: false, }; describe('PipelinesLogic', () => { @@ -86,6 +87,7 @@ describe('PipelinesLogic', () => { expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, + hasIndexIngestionPipeline: true, }); }); describe('makeRequest', () => { @@ -155,6 +157,7 @@ describe('PipelinesLogic', () => { connector: { ...connectorIndex.connector, pipeline: newPipeline }, }, pipelineState: newPipeline, + hasIndexIngestionPipeline: true, }); }); it('should not set configState if modal is open', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index e0ca6574b63d6..99d241507dd2a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -59,8 +59,8 @@ type PipelinesActions = Pick< Actions, 'apiError' | 'apiSuccess' | 'makeRequest' > & { - closeModal: () => void; closeAddMlInferencePipelineModal: () => void; + closeModal: () => void; createCustomPipeline: Actions< CreateCustomPipelineApiLogicArgs, CreateCustomPipelineApiLogicResponse @@ -94,12 +94,12 @@ type PipelinesActions = Pick< fetchIndexApiSuccess: Actions['apiSuccess']; fetchMlInferenceProcessors: typeof FetchMlInferencePipelineProcessorsApiLogic.actions.makeRequest; fetchMlInferenceProcessorsApiError: (error: HttpError) => HttpError; + openAddMlInferencePipelineModal: () => void; openModal: () => void; savePipeline: () => void; setPipelineState(pipeline: IngestPipelineParams): { pipeline: IngestPipelineParams; }; - openAddMlInferencePipelineModal: () => void; }; interface PipelinesValues { @@ -107,19 +107,20 @@ interface PipelinesValues { canUseMlInferencePipeline: boolean; defaultPipelineValues: IngestPipelineParams; defaultPipelineValuesData: IngestPipelineParams | null; + hasIndexIngestionPipeline: boolean; index: FetchIndexApiResponse; mlInferencePipelineProcessors: InferencePipeline[]; pipelineState: IngestPipelineParams; - showModal: boolean; showAddMlInferencePipelineModal: boolean; + showModal: boolean; } export const PipelinesLogic = kea>({ actions: { - closeModal: true, closeAddMlInferencePipelineModal: true, - openModal: true, + closeModal: true, openAddMlInferencePipelineModal: true, + openModal: true, savePipeline: true, setPipelineState: (pipeline: IngestPipelineParams) => ({ pipeline }), }, @@ -267,20 +268,20 @@ export const PipelinesLogic = kea pipeline, }, ], - showModal: [ + showAddMlInferencePipelineModal: [ false, { - apiSuccess: () => false, - closeModal: () => false, - openModal: () => true, + closeAddMlInferencePipelineModal: () => false, + createMlInferencePipelineSuccess: () => false, + openAddMlInferencePipelineModal: () => true, }, ], - showAddMlInferencePipelineModal: [ + showModal: [ false, { - createMlInferencePipelineSuccess: () => false, - closeAddMlInferencePipelineModal: () => false, - openAddMlInferencePipelineModal: () => true, + apiSuccess: () => false, + closeModal: () => false, + openModal: () => true, }, ], }), @@ -289,14 +290,26 @@ export const PipelinesLogic = kea [selectors.index], (index: ElasticsearchIndexWithIngestion) => !isApiIndex(index), ], - canUseMlInferencePipeline: [ - () => [selectors.canSetPipeline, selectors.pipelineState], - (canSetPipeline: boolean, pipelineState: IngestPipelineParams) => - canSetPipeline && pipelineState.run_ml_inference, - ], defaultPipelineValues: [ () => [selectors.defaultPipelineValuesData], (pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES, ], + hasIndexIngestionPipeline: [ + () => [selectors.pipelineState, selectors.defaultPipelineValues], + (pipelineState: IngestPipelineParams, defaultPipelineValues: IngestPipelineParams) => + pipelineState.name !== defaultPipelineValues.name, + ], + canUseMlInferencePipeline: [ + () => [ + selectors.canSetPipeline, + selectors.hasIndexIngestionPipeline, + selectors.pipelineState, + ], + ( + canSetPipeline: boolean, + hasIndexIngestionPipeline: boolean, + pipelineState: IngestPipelineParams + ) => canSetPipeline && hasIndexIngestionPipeline && pipelineState.run_ml_inference, + ], }), }); 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 59e7edf1d21d5..69bc3c4ab7e6e 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 @@ -46,6 +46,14 @@ describe('Setup Indices', () => { last_sync_status: { type: 'keyword' }, last_synced: { type: 'date' }, name: { type: 'keyword' }, + pipeline: { + properties: { + extract_binary_content: { type: 'boolean' }, + name: { type: 'keyword' }, + reduce_whitespace: { type: 'boolean' }, + run_ml_inference: { type: 'boolean' }, + }, + }, scheduling: { properties: { enabled: { type: 'boolean' }, 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 b564d519e73f9..bf6d2d3f96011 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 @@ -42,6 +42,14 @@ const connectorMappingsProperties: Record = { last_sync_status: { type: 'keyword' }, last_synced: { type: 'date' }, name: { type: 'keyword' }, + pipeline: { + properties: { + extract_binary_content: { type: 'boolean' }, + name: { type: 'keyword' }, + reduce_whitespace: { type: 'boolean' }, + run_ml_inference: { type: 'boolean' }, + }, + }, scheduling: { properties: { enabled: { type: 'boolean' }, @@ -70,7 +78,7 @@ export const defaultConnectorsPipelineMeta: DefaultConnectorsPipelineMeta = { default_extract_binary_content: true, default_name: 'ent-search-generic-ingestion', default_reduce_whitespace: true, - default_run_ml_inference: false, + default_run_ml_inference: true, }; const indices: IndexDefinition[] = [ diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts index 35bdbe0d64064..eb923a8f29852 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts @@ -42,8 +42,8 @@ const mockGetPipeline2 = { }, { inference: { - model_id: 'trained-model-id-1', inference_config: { regression: {} }, + model_id: 'trained-model-id-1', }, }, ], @@ -53,8 +53,8 @@ const mockGetPipeline2 = { processors: [ { inference: { - model_id: 'trained-model-id-2', inference_config: { regression: {} }, + model_id: 'trained-model-id-2', }, }, ], @@ -75,12 +75,16 @@ const mockGetTrainedModelsData = { count: 1, trained_model_configs: [ { + inference_config: { ner: {} }, model_id: 'trained-model-id-1', model_type: 'lang_ident', + tags: [], }, { + inference_config: { ner: {} }, model_id: 'trained-model-id-2', model_type: 'pytorch', + tags: [], }, ], }; @@ -92,26 +96,26 @@ const mockGetTrainedModelStats = { model_id: 'trained-model-id-1', }, { - model_id: 'trained-model-id-2', deployment_stats: { state: 'started', }, + model_id: 'trained-model-id-2', }, ], }; -const something = { +const trainedModelDataObject = { 'trained-model-id-1': { isDeployed: false, - modelType: 'lang_ident', pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', + types: ['lang_ident', 'ner'], }, 'trained-model-id-2': { isDeployed: true, - modelType: 'pytorch', pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', + types: ['pytorch', 'ner'], }, }; @@ -172,15 +176,15 @@ describe('fetchPipelineProcessorInferenceData lib function', () => { const expected = { 'trained-model-id-1': { isDeployed: false, - modelType: 'unknown', pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', + types: [], }, 'trained-model-id-2': { isDeployed: false, - modelType: 'unknown', pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', + types: [], }, }; @@ -219,19 +223,19 @@ describe('fetchAndAddTrainedModelData lib function', () => { const input = { 'trained-model-id-1': { isDeployed: false, - modelType: 'unknown', pipelineName: 'ml-inference-pipeline-1', trainedModelName: 'trained-model-id-1', + types: [], }, 'trained-model-id-2': { isDeployed: false, - modelType: 'unknown', pipelineName: 'ml-inference-pipeline-2', trainedModelName: 'trained-model-id-2', + types: [], }, } as Record; - const expected = something as Record; + const expected = trainedModelDataObject as Record; const response = await fetchAndAddTrainedModelData( mockClient as unknown as ElasticsearchClient, @@ -311,7 +315,7 @@ describe('fetchMlInferencePipelineProcessors lib function', () => { }); describe('when using an index that has an ml-inference pipeline', () => { - it('should return an empty array', async () => { + it('should return pipeline processor data for that pipeline', async () => { mockClient.ingest.getPipeline.mockImplementationOnce(() => Promise.resolve(mockGetPipeline)); mockClient.ingest.getPipeline.mockImplementationOnce(() => Promise.resolve({ 'ml-inference-pipeline-1': mockGetPipeline2['ml-inference-pipeline-1'] }) @@ -323,7 +327,7 @@ describe('fetchMlInferencePipelineProcessors lib function', () => { Promise.resolve(mockGetTrainedModelStats) ); - const expected = [something['trained-model-id-1']]; + const expected = [trainedModelDataObject['trained-model-id-1']] as InferencePipeline[]; const response = await fetchMlInferencePipelineProcessors( mockClient as unknown as ElasticsearchClient, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts index 48e626ff1cecd..cfc80fc0f8b8b 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts @@ -6,6 +6,7 @@ */ import { ElasticsearchClient } from '@kbn/core/server'; +import { BUILT_IN_MODEL_TAG } from '@kbn/ml-plugin/common/constants/data_frame_analytics'; import { InferencePipeline } from '../../../common/types/pipelines'; import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; @@ -53,9 +54,9 @@ export const fetchPipelineProcessorInferenceData = async ( if (trainedModelName) pipelineProcessorData[trainedModelName] = { isDeployed: false, - modelType: 'unknown', pipelineName: pipelineProcessorName, trainedModelName, + types: [], }; return pipelineProcessorData; @@ -79,7 +80,13 @@ export const fetchAndAddTrainedModelData = async ( const trainedModelName = trainedModelData.model_id; if (pipelineProcessorData.hasOwnProperty(trainedModelName)) { - pipelineProcessorData[trainedModelName].modelType = trainedModelData.model_type || 'unknown'; + const isBuiltIn = trainedModelData.tags.includes(BUILT_IN_MODEL_TAG); + + pipelineProcessorData[trainedModelName].types = [ + trainedModelData.model_type, + ...Object.keys(trainedModelData.inference_config || {}), + ...(isBuiltIn ? [BUILT_IN_MODEL_TAG] : []), + ].filter((type): type is string => type !== undefined); } }); diff --git a/x-pack/plugins/enterprise_search/server/ui_settings.ts b/x-pack/plugins/enterprise_search/server/ui_settings.ts index 0be413f8d9c6a..3334e625bc08f 100644 --- a/x-pack/plugins/enterprise_search/server/ui_settings.ts +++ b/x-pack/plugins/enterprise_search/server/ui_settings.ts @@ -11,7 +11,6 @@ import { i18n } from '@kbn/i18n'; import { enterpriseSearchFeatureId, - enableIndexPipelinesTab, enableBehavioralAnalyticsSection, } from '../common/ui_settings_keys'; @@ -31,16 +30,4 @@ export const uiSettings: Record> = { schema: schema.boolean(), value: false, }, - [enableIndexPipelinesTab]: { - category: [enterpriseSearchFeatureId], - description: i18n.translate('xpack.enterpriseSearch.uiSettings.indexPipelines.description', { - defaultMessage: 'Enable the new index pipelines tab in Enterprise Search.', - }), - name: i18n.translate('xpack.enterpriseSearch.uiSettings.indexPipelines.name', { - defaultMessage: 'Enable index pipelines', - }), - requiresPageReload: false, - schema: schema.boolean(), - value: false, - }, }; diff --git a/x-pack/plugins/files/common/api_routes.ts b/x-pack/plugins/files/common/api_routes.ts index 9eb6671465799..523fe2b2e7c8f 100644 --- a/x-pack/plugins/files/common/api_routes.ts +++ b/x-pack/plugins/files/common/api_routes.ts @@ -98,7 +98,7 @@ export type UpdateFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< export type UploadFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< { id: string }, - unknown, + { selfDestructOnAbort?: boolean }, { body: unknown }, { ok: true; diff --git a/x-pack/plugins/files/server/file_kinds_registry/index.ts b/x-pack/plugins/files/common/file_kinds_registry/index.ts similarity index 86% rename from x-pack/plugins/files/server/file_kinds_registry/index.ts rename to x-pack/plugins/files/common/file_kinds_registry/index.ts index bd550e43dc4b7..5df6744546c03 100644 --- a/x-pack/plugins/files/server/file_kinds_registry/index.ts +++ b/x-pack/plugins/files/common/file_kinds_registry/index.ts @@ -6,10 +6,7 @@ */ import { createGetterSetter } from '@kbn/kibana-utils-plugin/common'; import assert from 'assert'; -import { FileKind } from '../../common'; - -import { registerFileKindRoutes } from '../routes/file_kind'; -import { FilesRouter } from '../routes/types'; +import { FileKind } from '..'; export interface FileKindsRegistry { /** @@ -32,7 +29,7 @@ export interface FileKindsRegistry { * @internal */ export class FileKindsRegistryImpl implements FileKindsRegistry { - constructor(private readonly router: FilesRouter) {} + constructor(private readonly onRegister?: (fileKind: FileKind) => void) {} private readonly fileKinds = new Map(); @@ -48,7 +45,7 @@ export class FileKindsRegistryImpl implements FileKindsRegistry { } this.fileKinds.set(fileKind.id, fileKind); - registerFileKindRoutes(this.router, fileKind); + this.onRegister?.(fileKind); } get(id: string): FileKind { diff --git a/x-pack/plugins/files/kibana.json b/x-pack/plugins/files/kibana.json index d85e4403557e4..ad83b24ef0842 100755 --- a/x-pack/plugins/files/kibana.json +++ b/x-pack/plugins/files/kibana.json @@ -10,5 +10,6 @@ "server": true, "ui": true, "requiredPlugins": [], + "requiredBundles": ["kibanaUtils"], "optionalPlugins": ["security", "usageCollection"] } diff --git a/x-pack/plugins/files/public/components/context.tsx b/x-pack/plugins/files/public/components/context.tsx new file mode 100644 index 0000000000000..ceed14b52abbd --- /dev/null +++ b/x-pack/plugins/files/public/components/context.tsx @@ -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 React, { createContext, useContext, type FunctionComponent } from 'react'; +import { FileKindsRegistry, getFileKindsRegistry } from '../../common/file_kinds_registry'; + +export interface FilesContextValue { + registry: FileKindsRegistry; +} + +const FilesContextObject = createContext(null as unknown as FilesContextValue); + +export const useFilesContext = () => { + const ctx = useContext(FilesContextObject); + if (!ctx) { + throw new Error('FilesContext is not found!'); + } + return ctx; +}; + +export const FilesContext: FunctionComponent = ({ children }) => { + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/files/public/components/index.ts b/x-pack/plugins/files/public/components/index.ts index 9648d9e66c12f..533b37505b961 100644 --- a/x-pack/plugins/files/public/components/index.ts +++ b/x-pack/plugins/files/public/components/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { Image } from './image'; -export type { ImageProps } from './image'; +export { Image, type ImageProps } from './image'; +export { UploadFile, type UploadFileProps } from './upload_file'; +export { FilesContext } from './context'; diff --git a/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx new file mode 100644 index 0000000000000..33a579ccc4c5f --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/cancel_button.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 { EuiButtonEmpty } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { useBehaviorSubject } from '../../use_behavior_subject'; +import { useUploadState } from '../context'; +import { i18nTexts } from '../i18n_texts'; + +interface Props { + onClick: () => void; +} + +export const CancelButton: FunctionComponent = ({ onClick }) => { + const uploadState = useUploadState(); + const uploading = useBehaviorSubject(uploadState.uploading$); + return ( + + {i18nTexts.cancel} + + ); +}; diff --git a/x-pack/plugins/files/public/components/upload_file/components/clear_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/clear_button.tsx new file mode 100644 index 0000000000000..929fe0dcc65b0 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/clear_button.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonEmpty } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { i18nTexts } from '../i18n_texts'; + +interface Props { + onClick: () => void; +} + +export const ClearButton: FunctionComponent = ({ onClick }) => { + return ( + + {i18nTexts.clear} + + ); +}; diff --git a/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx new file mode 100644 index 0000000000000..6898adc9cce36 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiIcon, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { useBehaviorSubject } from '../../use_behavior_subject'; +import { useUploadState } from '../context'; +import { i18nTexts } from '../i18n_texts'; +import { UploadButton } from './upload_button'; +import { RetryButton } from './retry_button'; +import { CancelButton } from './cancel_button'; + +const { euiButtonHeightSmall } = euiThemeVars; + +interface Props { + onCancel: () => void; + onUpload: () => void; + immediate?: boolean; +} + +export const ControlButton: FunctionComponent = ({ onCancel, onUpload, immediate }) => { + const uploadState = useUploadState(); + const { + euiTheme: { size }, + } = useEuiTheme(); + const uploading = useBehaviorSubject(uploadState.uploading$); + const files = useObservable(uploadState.files$, []); + const done = useObservable(uploadState.done$); + const retry = Boolean(files.some((f) => f.status === 'upload_failed')); + + if (uploading) return ; + if (retry) return ; + if (!done && !immediate) return ; + + if (done) { + return ( + + + + ); + } + return null; +}; diff --git a/x-pack/plugins/files/public/components/upload_file/components/index.ts b/x-pack/plugins/files/public/components/upload_file/components/index.ts new file mode 100644 index 0000000000000..fbc7ffd8eda79 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ControlButton } from './control_button'; +export { ClearButton } from './clear_button'; diff --git a/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx new file mode 100644 index 0000000000000..55df91c5be3e8 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx @@ -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 { EuiButtonEmpty } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { useBehaviorSubject } from '../../use_behavior_subject'; +import { useUploadState } from '../context'; +import { i18nTexts } from '../i18n_texts'; + +interface Props { + onClick: () => void; +} + +export const RetryButton: FunctionComponent = ({ onClick }) => { + const uploadState = useUploadState(); + const uploading = useBehaviorSubject(uploadState.uploading$); + + return ( + + {i18nTexts.retry} + + ); +}; diff --git a/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx new file mode 100644 index 0000000000000..c9918874f7895 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { i18nTexts } from '../i18n_texts'; +import { useUploadState } from '../context'; +import { useBehaviorSubject } from '../../use_behavior_subject'; + +interface Props { + onClick: () => void; +} + +export const UploadButton: FunctionComponent = ({ onClick }) => { + const uploadState = useUploadState(); + const uploading = useBehaviorSubject(uploadState.uploading$); + const error = useBehaviorSubject(uploadState.error$); + const files = useObservable(uploadState.files$, []); + return ( + + {uploading ? i18nTexts.uploading : i18nTexts.upload} + + ); +}; diff --git a/x-pack/plugins/files/public/components/upload_file/context.ts b/x-pack/plugins/files/public/components/upload_file/context.ts new file mode 100644 index 0000000000000..e88f5d2441489 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/context.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 React from 'react'; +import type { UploadState } from './upload_state'; + +export const context = React.createContext(null); +export const useUploadState = () => React.useContext(context)!; diff --git a/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts b/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts new file mode 100644 index 0000000000000..2077d635d3050 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/i18n_texts.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 { i18n } from '@kbn/i18n'; + +export const i18nTexts = { + defaultPickerLabel: i18n.translate('xpack.files.uploadFile.defaultFilePickerLabel', { + defaultMessage: 'Upload a file', + }), + upload: i18n.translate('xpack.files.uploadFile.uploadButtonLabel', { + defaultMessage: 'Upload', + }), + uploading: i18n.translate('xpack.files.uploadFile.uploadingButtonLabel', { + defaultMessage: 'Uploading', + }), + retry: i18n.translate('xpack.files.uploadFile.retryButtonLabel', { + defaultMessage: 'Retry', + }), + clear: i18n.translate('xpack.files.uploadFile.clearButtonLabel', { + defaultMessage: 'Clear', + }), + cancel: i18n.translate('xpack.files.uploadFile.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), + uploadDone: i18n.translate('xpack.files.uploadFile.uploadDoneToolTipContent', { + defaultMessage: 'Your file was successfully uploaded!', + }), + fileTooLarge: (expectedSize: string) => + i18n.translate('xpack.files.uploadFile.fileTooLargeErrorMessage', { + defaultMessage: + 'File is too large. Maximum size is {expectedSize, plural, one {# byte} other {# bytes} }.', + values: { expectedSize }, + }), +}; diff --git a/x-pack/plugins/files/public/components/upload_file/index.tsx b/x-pack/plugins/files/public/components/upload_file/index.tsx new file mode 100644 index 0000000000000..4901c46a78c91 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/index.tsx @@ -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 React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import type { Props } from './upload_file'; + +export type { Props as UploadFileProps }; + +const UploadFileContainer = lazy(() => import('./upload_file')); + +export const UploadFile = (props: Props) => ( + }> + + +); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx new file mode 100644 index 0000000000000..f4f5986d2f00b --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiText, + EuiSpacer, + EuiFlexItem, + EuiFlexGroup, + EuiFilePicker, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; +import useObservable from 'react-use/lib/useObservable'; +import { useBehaviorSubject } from '../use_behavior_subject'; +import { i18nTexts } from './i18n_texts'; +import { ControlButton, ClearButton } from './components'; +import { useUploadState } from './context'; + +export interface Props { + meta?: unknown; + accept?: string; + immediate?: boolean; + allowClear?: boolean; + initialFilePromptText?: string; +} + +const { euiFormMaxWidth, euiButtonHeightSmall } = euiThemeVars; + +export const UploadFile = React.forwardRef( + ({ meta, accept, immediate, allowClear = false, initialFilePromptText }, ref) => { + const uploadState = useUploadState(); + const uploading = useBehaviorSubject(uploadState.uploading$); + const error = useBehaviorSubject(uploadState.error$); + const done = useObservable(uploadState.done$); + const isInvalid = Boolean(error); + const errorMessage = error?.message; + + const id = useGeneratedHtmlId({ prefix: 'filesUploadFile' }); + const errorId = `${id}_error`; + + return ( +
+ { + uploadState.setFiles(Array.from(fs ?? [])); + if (immediate) uploadState.upload(meta); + }} + multiple={false} + initialPromptText={initialFilePromptText} + isLoading={uploading} + isInvalid={isInvalid} + accept={accept} + disabled={Boolean(done?.length || uploading)} + aria-describedby={errorMessage ? errorId : undefined} + /> + + + + + + uploadState.upload(meta)} + /> + + {Boolean(!done && !uploading && errorMessage) && ( + + + {errorMessage} + + + )} + {done?.length && allowClear && ( + <> + {/* Occupy middle space */} + + + + + )} + +
+ ); + } +); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx new file mode 100644 index 0000000000000..c5a64d6d91a52 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.stories.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 from 'react'; +import { ComponentStory } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { + FileKindsRegistryImpl, + setFileKindsRegistry, + getFileKindsRegistry, +} from '../../../common/file_kinds_registry'; +import { FilesClient } from '../../types'; +import { FilesContext } from '../context'; +import { UploadFile, Props } from './upload_file'; + +const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); +const kind = 'test'; + +const defaultArgs: Props = { + kind, + onDone: action('onDone'), + onError: action('onError'), + client: { + create: async () => ({ file: { id: 'test' } }), + upload: () => sleep(1000), + } as unknown as FilesClient, +}; + +export default { + title: 'stateful/UploadFile', + component: UploadFile, + args: defaultArgs, +}; + +setFileKindsRegistry(new FileKindsRegistryImpl()); + +getFileKindsRegistry().register({ + id: kind, + http: {}, + allowedMimeTypes: ['*'], +}); + +const miniFile = 'miniFile'; +getFileKindsRegistry().register({ + id: miniFile, + http: {}, + maxSizeBytes: 1, + allowedMimeTypes: ['*'], +}); + +const zipOnly = 'zipOnly'; +getFileKindsRegistry().register({ + id: zipOnly, + http: {}, + allowedMimeTypes: ['application/zip'], +}); + +const Template: ComponentStory = (props: Props) => ( + + + +); + +export const Basic = Template.bind({}); + +export const AllowRepeatedUploads = Template.bind({}); +AllowRepeatedUploads.args = { + allowRepeatedUploads: true, +}; + +export const LongErrorUX = Template.bind({}); +LongErrorUX.args = { + client: { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(1000); + throw new Error('Something went wrong while uploading! '.repeat(10).trim()); + }, + delete: async () => {}, + } as unknown as FilesClient, +}; + +export const Abort = Template.bind({}); +Abort.args = { + client: { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(60000); + }, + delete: async () => {}, + } as unknown as FilesClient, +}; + +export const MaxSize = Template.bind({}); +MaxSize.args = { + kind: miniFile, +}; + +export const ZipOnly = Template.bind({}); +ZipOnly.args = { + kind: zipOnly, +}; + +export const AllowClearAfterUpload = Template.bind({}); +AllowClearAfterUpload.args = { + allowClear: true, +}; + +export const ImmediateUpload = Template.bind({}); +ImmediateUpload.args = { + immediate: true, +}; + +export const ImmediateUploadError = Template.bind({}); +ImmediateUploadError.args = { + immediate: true, + client: { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(1000); + throw new Error('Something went wrong while uploading!'); + }, + delete: async () => {}, + } as unknown as FilesClient, +}; + +export const ImmediateUploadAbort = Template.bind({}); +ImmediateUploadAbort.args = { + immediate: true, + client: { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(60000); + }, + delete: async () => {}, + } as unknown as FilesClient, +}; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx new file mode 100644 index 0000000000000..1812f74e180e3 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { act } from 'react-dom/test-utils'; +import { registerTestBed } from '@kbn/test-jest-helpers'; +import { EuiFilePicker } from '@elastic/eui'; + +import { + FileKindsRegistryImpl, + setFileKindsRegistry, + getFileKindsRegistry, +} from '../../../common/file_kinds_registry'; + +import { createMockFilesClient } from '../../mocks'; + +import { FileJSON } from '../../../common'; +import { FilesContext } from '../context'; +import { UploadFile, Props } from './upload_file'; + +describe('UploadFile', () => { + const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); + let onDone: jest.Mock; + let onError: jest.Mock; + let client: ReturnType; + + async function initTestBed(props?: Partial) { + const createTestBed = registerTestBed((p: Props) => ( + + + + )); + + const testBed = await createTestBed({ + client, + kind: 'test', + onDone, + onError, + ...props, + }); + + const baseTestSubj = `filesUploadFile`; + + const testSubjects = { + base: baseTestSubj, + uploadButton: `${baseTestSubj}.uploadButton`, + retryButton: `${baseTestSubj}.retryButton`, + cancelButton: `${baseTestSubj}.cancelButton`, + errorMessage: `${baseTestSubj}.error`, + successIcon: `${baseTestSubj}.uploadSuccessIcon`, + }; + + return { + ...testBed, + actions: { + addFiles: (files: File[]) => + act(async () => { + testBed.component.find(EuiFilePicker).props().onChange!(files as unknown as FileList); + await sleep(1); + testBed.component.update(); + }), + upload: (retry = false) => + act(async () => { + testBed + .find(retry ? testSubjects.retryButton : testSubjects.uploadButton) + .simulate('click'); + await sleep(1); + testBed.component.update(); + }), + abort: () => + act(() => { + testBed.find(testSubjects.cancelButton).simulate('click'); + testBed.component.update(); + }), + wait: (ms: number) => + act(async () => { + await sleep(ms); + testBed.component.update(); + }), + }, + testSubjects, + }; + } + + beforeAll(() => { + setFileKindsRegistry(new FileKindsRegistryImpl()); + getFileKindsRegistry().register({ + id: 'test', + maxSizeBytes: 10000, + http: {}, + }); + }); + + beforeEach(() => { + client = createMockFilesClient(); + onDone = jest.fn(); + onError = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('shows the success message when upload completes', async () => { + client.create.mockResolvedValue({ file: { id: 'test', size: 1 } as FileJSON }); + client.upload.mockResolvedValue({ size: 1, ok: true }); + + const { actions, exists, testSubjects } = await initTestBed(); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + await actions.upload(); + await sleep(1000); + expect(exists(testSubjects.errorMessage)).toBe(false); + expect(exists(testSubjects.successIcon)).toBe(true); + expect(onDone).toHaveBeenCalledTimes(1); + }); + + it('does not show the upload button for "immediate" uploads', async () => { + client.create.mockResolvedValue({ file: { id: 'test' } as FileJSON }); + client.upload.mockImplementation(() => sleep(100).then(() => ({ ok: true, size: 1 }))); + + const { actions, exists, testSubjects } = await initTestBed({ onDone, immediate: true }); + expect(exists(testSubjects.uploadButton)).toBe(false); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + expect(exists(testSubjects.uploadButton)).toBe(false); + await actions.wait(100); + + expect(onDone).toHaveBeenCalledTimes(1); + expect(onError).not.toHaveBeenCalled(); + }); + + it('allows users to cancel uploads', async () => { + client.create.mockResolvedValue({ file: { id: 'test' } as FileJSON }); + client.upload.mockImplementation(() => sleep(1000).then(() => ({ ok: true, size: 1 }))); + + const { actions, testSubjects, find } = await initTestBed(); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + await actions.upload(); + expect(find(testSubjects.cancelButton).props().disabled).toBe(false); + actions.abort(); + + await sleep(1000); + + expect(onDone).not.toHaveBeenCalled(); + expect(onError).not.toHaveBeenCalled(); + }); + + it('does not show error messages while loading', async () => { + client.create.mockResolvedValue({ file: { id: 'test' } as FileJSON }); + client.upload.mockImplementation(async () => { + await sleep(100); + throw new Error('stop!'); + }); + + const { actions, exists, testSubjects } = await initTestBed(); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.upload(); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.wait(1000); + expect(exists(testSubjects.uploadButton)).toBe(false); // No upload button + expect(exists(testSubjects.errorMessage)).toBe(true); + await actions.upload(true); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.wait(500); + expect(exists(testSubjects.errorMessage)).toBe(true); + + expect(onDone).not.toHaveBeenCalled(); + }); + + it('shows error messages if there are any', async () => { + client.create.mockResolvedValue({ file: { id: 'test', size: 10001 } as FileJSON }); + client.upload.mockImplementation(async () => { + await sleep(100); + throw new Error('stop!'); + }); + + const { actions, exists, testSubjects, find } = await initTestBed(); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + await actions.upload(); + await actions.wait(1000); + expect(find(testSubjects.errorMessage).text()).toMatch(/stop/i); + expect(onDone).not.toHaveBeenCalled(); + }); + + it('prevents uploads if there is an issue', async () => { + client.create.mockResolvedValue({ file: { id: 'test', size: 10001 } as FileJSON }); + + const { actions, exists, testSubjects, find } = await initTestBed(); + expect(exists(testSubjects.errorMessage)).toBe(false); + await actions.addFiles([{ name: 'test', size: 10001 } as File]); + expect(exists(testSubjects.errorMessage)).toBe(true); + expect(find(testSubjects.errorMessage).text()).toMatch(/File is too large/); + + expect(onDone).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx new file mode 100644 index 0000000000000..03df4434c548a --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.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 { EuiFilePicker } from '@elastic/eui'; +import React, { type FunctionComponent, useRef, useEffect, useMemo } from 'react'; +import { FilesClient } from '../../types'; + +import { useFilesContext } from '../context'; + +import { UploadFile as Component } from './upload_file.component'; +import { createUploadState } from './upload_state'; +import { context } from './context'; + +/** + * An object representing an uploadded file + */ +interface UploadedFile { + /** + * The ID that was generated for the uploaded file + */ + id: string; + /** + * The kind of the file that was passed in to this component + */ + kind: string; +} + +/** + * UploadFile component props + */ +export interface Props { + /** + * A file kind that should be registered during plugin startup. See {@link FileServiceStart}. + */ + kind: Kind; + /** + * A files client that will be used process uploads. + */ + client: FilesClient; + /** + * Allow users to clear a file after uploading. + * + * @note this will NOT delete an uploaded file. + */ + allowClear?: boolean; + /** + * Start uploading the file as soon as it is provided + * by the user. + */ + immediate?: boolean; + /** + * Metadata that you want to associate with any uploaded files + */ + meta?: Record; + /** + * Whether this component should display a "done" state after processing an + * upload or return to the initial state to allow for another upload. + * + * @default false + */ + allowRepeatedUploads?: boolean; + /** + * Called when the an upload process fully completes + */ + onDone: (files: UploadedFile[]) => void; + + /** + * Called when an error occurs during upload + */ + onError?: (e: Error) => void; +} + +/** + * This component is intended as a wrapper around EuiFilePicker with some opinions + * about upload UX. It is optimised for use in modals, flyouts or forms. + * + * In order to use this component you must register your file kind with {@link FileKindsRegistry} + */ +export const UploadFile = ({ + meta, + client, + onDone, + onError, + allowClear, + kind: kindId, + immediate = false, + allowRepeatedUploads = false, +}: Props): ReturnType => { + const { registry } = useFilesContext(); + const ref = useRef(null); + const uploadState = useMemo( + () => + createUploadState({ + client, + fileKind: registry.get(kindId), + allowRepeatedUploads, + }), + [client, kindId, allowRepeatedUploads, registry] + ); + + /** + * Hook state into component callbacks + */ + useEffect(() => { + const subs = [ + uploadState.clear$.subscribe(() => { + ref.current?.removeFiles(); + }), + uploadState.done$.subscribe((n) => n && onDone(n)), + uploadState.error$.subscribe((e) => e && onError?.(e)), + ]; + return () => subs.forEach((sub) => sub.unsubscribe()); + }, [uploadState, onDone, onError]); + + return ( + + + + ); +}; + +/* eslint-disable import/no-default-export */ +export default UploadFile; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts new file mode 100644 index 0000000000000..9d7ca8a468be1 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { of, delay, merge, tap, mergeMap } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import type { FileKind, FileJSON } from '../../../common'; +import { createMockFilesClient } from '../../mocks'; +import type { FilesClient } from '../../types'; + +import { UploadState } from './upload_state'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => expect(actual).toEqual(expected)); + +describe('UploadState', () => { + let filesClient: DeeplyMockedKeys; + let uploadState: UploadState; + let testScheduler: TestScheduler; + + beforeEach(() => { + filesClient = createMockFilesClient(); + filesClient.create.mockReturnValue(of({ file: { id: 'test' } as FileJSON }) as any); + filesClient.upload.mockReturnValue(of(undefined) as any); + uploadState = new UploadState( + { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, + filesClient + ); + testScheduler = getTestScheduler(); + }); + + it('uploads all provided files and reports errors', async () => { + testScheduler.run(({ expectObservable, cold, flush }) => { + const file1 = { name: 'test', size: 1 } as File; + const file2 = { name: 'test 2', size: 1 } as File; + + uploadState.setFiles([file1, file2]); + + // Simulate upload being triggered async + const upload$ = cold('--a|').pipe(tap(uploadState.upload)); + + expectObservable(upload$).toBe('--a|'); + + expectObservable(uploadState.uploading$).toBe('a-(bc)', { + a: false, + b: true, + c: false, + }); + + expectObservable(uploadState.files$).toBe('a-(bc)', { + a: [ + { file: file1, status: 'idle' }, + { file: file2, status: 'idle' }, + ], + b: [ + { file: file1, status: 'uploading' }, + { file: file2, status: 'uploading' }, + ], + c: [ + { file: file1, status: 'uploaded', id: 'test' }, + { file: file2, status: 'uploaded', id: 'test' }, + ], + }); + + flush(); + + expect(filesClient.create).toHaveBeenCalledTimes(2); + expect(filesClient.upload).toHaveBeenCalledTimes(2); + expect(filesClient.delete).not.toHaveBeenCalled(); + }); + }); + + it('attempts to clean up all files when aborting', async () => { + testScheduler.run(({ expectObservable, cold, flush }) => { + filesClient.create.mockReturnValue( + of({ file: { id: 'test' } as FileJSON }).pipe(delay(2)) as any + ); + filesClient.upload.mockReturnValue(of(undefined).pipe(delay(10)) as any); + filesClient.delete.mockReturnValue(of(undefined) as any); + + const file1 = { name: 'test' } as File; + const file2 = { name: 'test 2.png' } as File; + + uploadState.setFiles([file1, file2]); + + // Simulate upload being triggered async + const upload$ = cold('-0|').pipe(tap(() => uploadState.upload({ myMeta: true }))); + const abort$ = cold(' --1|').pipe(tap(uploadState.abort)); + + expectObservable(merge(upload$, abort$)).toBe('-01|'); + + expectObservable(uploadState.error$).toBe('0---', [undefined]); + + expectObservable(uploadState.uploading$).toBe('ab-c', { + a: false, + b: true, + c: false, + }); + + expectObservable(uploadState.files$).toBe('ab-c', { + a: [ + { file: file1, status: 'idle' }, + { file: file2, status: 'idle' }, + ], + b: [ + { file: file1, status: 'uploading' }, + { file: file2, status: 'uploading' }, + ], + c: [ + { file: file1, status: 'upload_failed' }, + { file: file2, status: 'upload_failed' }, + ], + }); + + flush(); + + expect(filesClient.create).toHaveBeenCalledTimes(2); + expect(filesClient.create).toHaveBeenNthCalledWith(1, { + kind: 'test', + meta: { myMeta: true }, + mimeType: undefined, + name: 'test', + }); + expect(filesClient.create).toHaveBeenNthCalledWith(2, { + kind: 'test', + meta: { myMeta: true }, + mimeType: 'image/png', + name: 'test 2', + }); + expect(filesClient.upload).toHaveBeenCalledTimes(2); + expect(filesClient.delete).toHaveBeenCalledTimes(2); + }); + }); + + it('throws for files that are too large', () => { + testScheduler.run(({ expectObservable }) => { + const file = { + name: 'test', + size: 1001, + } as File; + uploadState.setFiles([file]); + expectObservable(uploadState.files$).toBe('a', { + a: [ + { + file, + status: 'idle', + error: new Error('File is too large. Maximum size is 1,000 bytes.'), + }, + ], + }); + }); + }); + + it('option "allowRepeatedUploads" calls clear after upload is done', () => { + testScheduler.run(({ expectObservable, cold }) => { + uploadState = new UploadState( + { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, + filesClient, + { allowRepeatedUploads: true } + ); + const file1 = { name: 'test' } as File; + const file2 = { name: 'test 2.png' } as File; + + uploadState.setFiles([file1, file2]); + + const upload$ = cold('-0|').pipe(mergeMap(() => uploadState.upload({ myMeta: true }))); + expectObservable(upload$, ' --^').toBe('---0|', [undefined]); + expectObservable(uploadState.clear$, '^').toBe(' ---0-', [undefined]); + }); + }); +}); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.ts new file mode 100644 index 0000000000000..aa79db50a1446 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.ts @@ -0,0 +1,247 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + of, + map, + zip, + from, + race, + take, + filter, + Subject, + finalize, + forkJoin, + mergeMap, + switchMap, + catchError, + shareReplay, + ReplaySubject, + BehaviorSubject, + type Observable, + combineLatest, + distinctUntilChanged, +} from 'rxjs'; +import type { FileKind, FileJSON } from '../../../common/types'; +import type { FilesClient } from '../../types'; +import { i18nTexts } from './i18n_texts'; + +import { createStateSubject, type SimpleStateSubject, parseFileName } from './util'; + +interface FileState { + file: File; + status: 'idle' | 'uploading' | 'uploaded' | 'upload_failed'; + id?: string; + error?: Error; +} + +type Upload = SimpleStateSubject; + +interface DoneNotification { + id: string; + kind: string; +} + +interface UploadOptions { + allowRepeatedUploads?: boolean; +} + +export class UploadState { + private readonly abort$ = new Subject(); + private readonly files$$ = new BehaviorSubject([]); + + public readonly files$ = this.files$$.pipe( + switchMap((files$) => (files$.length ? zip(...files$) : of([]))) + ); + public readonly clear$ = new Subject(); + public readonly error$ = new BehaviorSubject(undefined); + public readonly uploading$ = new BehaviorSubject(false); + public readonly done$ = new Subject(); + + constructor( + private readonly fileKind: FileKind, + private readonly client: FilesClient, + private readonly opts: UploadOptions = { allowRepeatedUploads: false } + ) { + const latestFiles$ = this.files$$.pipe(switchMap((files$) => combineLatest(files$))); + + latestFiles$ + .pipe( + map((files) => files.some((file) => file.status === 'uploading')), + distinctUntilChanged() + ) + .subscribe(this.uploading$); + + latestFiles$ + .pipe( + map((files) => { + const errorFile = files.find((file) => Boolean(file.error)); + return errorFile ? errorFile.error : undefined; + }), + filter(Boolean) + ) + .subscribe(this.error$); + + latestFiles$ + .pipe( + filter( + (files) => Boolean(files.length) && files.every((file) => file.status === 'uploaded') + ), + map((files) => files.map((file) => ({ id: file.id!, kind: this.fileKind.id }))) + ) + .subscribe(this.done$); + } + + public isUploading(): boolean { + return this.uploading$.getValue(); + } + + private validateFiles(files: File[]): undefined | string { + if ( + this.fileKind.maxSizeBytes != null && + files.some((file) => file.size > this.fileKind.maxSizeBytes!) + ) { + return i18nTexts.fileTooLarge(String(this.fileKind.maxSizeBytes)); + } + return; + } + + public setFiles = (files: File[]): void => { + if (this.isUploading()) { + throw new Error('Cannot update files while uploading'); + } + + if (!files.length) { + this.done$.next(undefined); + this.error$.next(undefined); + } + + const validationError = this.validateFiles(files); + + this.files$$.next( + files.map((file) => + createStateSubject({ + file, + status: 'idle', + error: validationError ? new Error(validationError) : undefined, + }) + ) + ); + }; + + public abort = (): void => { + if (!this.isUploading()) { + throw new Error('No upload in progress'); + } + this.abort$.next(); + }; + + clear = (): void => { + this.setFiles([]); + this.clear$.next(); + }; + + /** + * Do not throw from this method, it is intended to work with {@link forkJoin} from rxjs which + * unsubscribes from all observables if one of them throws. + */ + private uploadFile = ( + file$: SimpleStateSubject, + abort$: Observable, + meta?: unknown + ): Observable => { + const abortController = new AbortController(); + const abortSignal = abortController.signal; + const { file, status } = file$.getValue(); + if (!['idle', 'upload_failed'].includes(status)) { + return of(undefined); + } + + let uploadTarget: undefined | FileJSON; + let erroredOrAborted = false; + + file$.setState({ status: 'uploading', error: undefined }); + + const { name, mime } = parseFileName(file.name); + + return from( + this.client.create({ + kind: this.fileKind.id, + name, + mimeType: mime, + meta: meta as Record, + }) + ).pipe( + mergeMap((result) => { + uploadTarget = result.file; + return race( + abort$.pipe( + map(() => { + abortController.abort(); + throw new Error('Abort!'); + }) + ), + this.client.upload({ + body: file, + id: uploadTarget.id, + kind: this.fileKind.id, + abortSignal, + }) + ); + }), + map(() => { + file$.setState({ status: 'uploaded', id: uploadTarget?.id }); + }), + catchError((e) => { + erroredOrAborted = true; + const isAbortError = e.message === 'Abort!'; + file$.setState({ status: 'upload_failed', error: isAbortError ? undefined : e }); + return of(isAbortError ? undefined : e); + }), + finalize(() => { + if (erroredOrAborted && uploadTarget) { + this.client.delete({ id: uploadTarget.id, kind: this.fileKind.id }); + } + }) + ); + }; + + public upload = (meta?: unknown): Observable => { + if (this.isUploading()) { + throw new Error('Upload already in progress'); + } + const abort$ = new ReplaySubject(1); + const sub = this.abort$.subscribe(abort$); + const upload$ = this.files$$.pipe( + take(1), + switchMap((files$) => { + return forkJoin(files$.map((file$) => this.uploadFile(file$, abort$, meta))); + }), + map(() => undefined), + finalize(() => { + if (this.opts.allowRepeatedUploads) this.clear(); + sub.unsubscribe(); + }), + shareReplay() + ); + + upload$.subscribe(); + + return upload$; + }; +} + +export const createUploadState = ({ + fileKind, + client, + ...options +}: { + fileKind: FileKind; + client: FilesClient; +} & UploadOptions) => { + return new UploadState(fileKind, client, options); +}; diff --git a/x-pack/plugins/files/public/components/upload_file/util/index.ts b/x-pack/plugins/files/public/components/upload_file/util/index.ts new file mode 100644 index 0000000000000..e8fbb4e1ecedc --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/util/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 { SimpleStateSubject, createStateSubject } from './simple_state_subject'; + +export { parseFileName } from './parse_file_name'; diff --git a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.ts new file mode 100644 index 0000000000000..f03b019f6aca3 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.test.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 { parseFileName } from './parse_file_name'; + +describe('parseFileName', () => { + test('file.png', () => { + expect(parseFileName('file.png')).toEqual({ + name: 'file', + mime: 'image/png', + }); + }); + + test(' Something_* really -=- strange.abc.wav', () => { + expect(parseFileName(' Something_* really -=- strange.abc.wav')).toEqual({ + name: 'Something__ really ___ strange_abc', + mime: 'audio/wave', + }); + }); + + test('!@#$%^&*()', () => { + expect(parseFileName('!@#$%^&*()')).toEqual({ + name: '__________', + mime: undefined, + }); + }); + + test('reallylong.repeat(100).dmg', () => { + expect(parseFileName('reallylong'.repeat(100) + '.dmg')).toEqual({ + name: 'reallylong'.repeat(100).slice(0, 256), + mime: 'application/x-apple-diskimage', + }); + }); +}); diff --git a/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.ts new file mode 100644 index 0000000000000..22e6833851825 --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/util/parse_file_name.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 mime from 'mime-types'; + +interface Result { + name: string; + mime?: string; +} + +export function parseFileName(fileName: string): Result { + const withoutExt = fileName.substring(0, fileName.lastIndexOf('.')) || fileName; + return { + name: withoutExt + .trim() + .slice(0, 256) + .replace(/[^a-z0-9\s]/gi, '_'), // replace invalid chars + mime: mime.lookup(fileName) || undefined, + }; +} diff --git a/x-pack/plugins/files/public/components/upload_file/util/simple_state_subject.ts b/x-pack/plugins/files/public/components/upload_file/util/simple_state_subject.ts new file mode 100644 index 0000000000000..a55259b4962ed --- /dev/null +++ b/x-pack/plugins/files/public/components/upload_file/util/simple_state_subject.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 { merge } from 'lodash'; +import { BehaviorSubject } from 'rxjs'; + +export class SimpleStateSubject extends BehaviorSubject { + constructor(initialState: S) { + super(initialState); + } + + public getSnapshot() { + return this.getValue(); + } + + public setState(nextState: Partial): void { + this.next(merge({}, this.getSnapshot(), nextState)); + } +} + +export const createStateSubject = (initialState: S) => + new SimpleStateSubject(initialState); diff --git a/x-pack/plugins/files/public/components/use_behavior_subject.ts b/x-pack/plugins/files/public/components/use_behavior_subject.ts new file mode 100644 index 0000000000000..f68ae6faef28c --- /dev/null +++ b/x-pack/plugins/files/public/components/use_behavior_subject.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 { BehaviorSubject } from 'rxjs'; +import useObservable from 'react-use/lib/useObservable'; + +export function useBehaviorSubject(o$: BehaviorSubject) { + return useObservable(o$, o$.getValue()); +} diff --git a/x-pack/plugins/files/public/files_client/files_client.ts b/x-pack/plugins/files/public/files_client/files_client.ts index 922846cde2aaf..04e872c654cfc 100644 --- a/x-pack/plugins/files/public/files_client/files_client.ts +++ b/x-pack/plugins/files/public/files_client/files_client.ts @@ -127,12 +127,12 @@ export function createFilesClient({ body: JSON.stringify(body), }); }, - upload: ({ kind, ...args }) => { + upload: ({ kind, abortSignal, ...args }) => { return http.put(apiRoutes.getUploadRoute(scopedFileKind ?? kind, args.id), { headers: { 'Content-Type': 'application/octet-stream', }, - + signal: abortSignal, body: args.body as BodyInit, }); }, diff --git a/x-pack/plugins/files/public/index.ts b/x-pack/plugins/files/public/index.ts index 0c8d9dd242b51..f08b073e7485b 100644 --- a/x-pack/plugins/files/public/index.ts +++ b/x-pack/plugins/files/public/index.ts @@ -13,6 +13,13 @@ export type { FilesClientFactory, FilesClientResponses, } from './types'; +export { + FilesContext, + Image, + type ImageProps, + UploadFile, + type UploadFileProps, +} from './components'; export function plugin() { return new FilesPlugin(); diff --git a/x-pack/plugins/files/public/mocks.ts b/x-pack/plugins/files/public/mocks.ts new file mode 100644 index 0000000000000..c34438d096a2b --- /dev/null +++ b/x-pack/plugins/files/public/mocks.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 type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import type { FilesClient } from './types'; + +// TODO: Remove this once we have access to the shared file client mock +export const createMockFilesClient = (): DeeplyMockedKeys => ({ + create: jest.fn(), + delete: jest.fn(), + download: jest.fn(), + find: jest.fn(), + getById: jest.fn(), + getDownloadHref: jest.fn(), + getMetrics: jest.fn(), + getShare: jest.fn(), + list: jest.fn(), + listShares: jest.fn(), + publicDownload: jest.fn(), + share: jest.fn(), + unshare: jest.fn(), + update: jest.fn(), + upload: jest.fn(), +}); diff --git a/x-pack/plugins/files/public/plugin.ts b/x-pack/plugins/files/public/plugin.ts index 22276ba377d8e..688ce0ecbb6a5 100644 --- a/x-pack/plugins/files/public/plugin.ts +++ b/x-pack/plugins/files/public/plugin.ts @@ -6,8 +6,14 @@ */ import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + getFileKindsRegistry, + setFileKindsRegistry, + FileKindsRegistryImpl, +} from '../common/file_kinds_registry'; import type { FilesClientFactory } from './types'; import { createFilesClient } from './files_client'; +import { FileKind } from '../common'; /** * Public setup-phase contract @@ -18,31 +24,48 @@ export interface FilesSetup { * registered {@link FileKind}. */ filesClientFactory: FilesClientFactory; + + /** + * Register a {@link FileKind} which allows for specifying details about the files + * that will be uploaded. + * + * @param {FileKind} fileKind - the file kind to register + */ + registerFileKind(fileKind: FileKind): void; } -export type FilesStart = FilesSetup; +export type FilesStart = Pick; /** * Bringing files to Kibana */ export class FilesPlugin implements Plugin { - private api: undefined | FilesSetup; + private filesClientFactory: undefined | FilesClientFactory; + + constructor() { + setFileKindsRegistry(new FileKindsRegistryImpl()); + } setup(core: CoreSetup): FilesSetup { - this.api = { - filesClientFactory: { - asScoped(fileKind: string) { - return createFilesClient({ fileKind, http: core.http }); - }, - asUnscoped() { - return createFilesClient({ http: core.http }); - }, + this.filesClientFactory = { + asScoped(fileKind: string) { + return createFilesClient({ fileKind, http: core.http }); + }, + asUnscoped() { + return createFilesClient({ http: core.http }); + }, + }; + return { + filesClientFactory: this.filesClientFactory, + registerFileKind: (fileKind: FileKind) => { + getFileKindsRegistry().register(fileKind); }, }; - return this.api; } start(core: CoreStart): FilesStart { - return this.api!; + return { + filesClientFactory: this.filesClientFactory!, + }; } } diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index 8a2a67bea8e93..e7a7bd24ce761 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -31,8 +31,8 @@ type UnscopedClientMethodFrom = ( /** * @param args - Input to the endpoint which includes body, params and query of the RESTful endpoint. */ -type ClientMethodFrom = ( - args: Parameters>[0] & { kind: string } +type ClientMethodFrom = ( + args: Parameters>[0] & { kind: string } & ExtraArgs ) => Promise; interface GlobalEndpoints { @@ -96,7 +96,7 @@ export interface FilesClient extends GlobalEndpoints { * * @param args - upload file args */ - upload: ClientMethodFrom; + upload: ClientMethodFrom; /** * Stream a download of the file object's content. * diff --git a/x-pack/plugins/files/server/file/file.test.ts b/x-pack/plugins/files/server/file/file.test.ts index 69f96be79f08b..b2e9167c685b3 100644 --- a/x-pack/plugins/files/server/file/file.test.ts +++ b/x-pack/plugins/files/server/file/file.test.ts @@ -11,7 +11,6 @@ import { elasticsearchServiceMock, loggingSystemMock, savedObjectsServiceMock, - httpServiceMock, } from '@kbn/core/server/mocks'; import { Readable } from 'stream'; import { promisify } from 'util'; @@ -24,7 +23,7 @@ import { FileKindsRegistryImpl, getFileKindsRegistry, setFileKindsRegistry, -} from '../file_kinds_registry'; +} from '../../common/file_kinds_registry'; import { InternalFileShareService } from '../file_share_service'; import { FileMetadataClient } from '../file_client'; import { SavedObjectsFileMetadataClient } from '../file_client/file_metadata_client/adapters/saved_objects'; @@ -41,7 +40,7 @@ describe('File', () => { const fileKind = 'fileKind'; beforeAll(() => { - setFileKindsRegistry(new FileKindsRegistryImpl(httpServiceMock.createRouter())); + setFileKindsRegistry(new FileKindsRegistryImpl()); getFileKindsRegistry().register({ http: {}, id: fileKind }); }); diff --git a/x-pack/plugins/files/server/file_client/file_client.ts b/x-pack/plugins/files/server/file_client/file_client.ts index 595d0095a6625..ca806b6644f42 100644 --- a/x-pack/plugins/files/server/file_client/file_client.ts +++ b/x-pack/plugins/files/server/file_client/file_client.ts @@ -8,8 +8,9 @@ import moment from 'moment'; import { Readable } from 'stream'; import mimeType from 'mime'; import cuid from 'cuid'; -import type { Logger } from '@kbn/core/server'; +import { type Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { AuditLogger } from '@kbn/security-plugin/server'; +import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import type { File, FileJSON, @@ -23,6 +24,7 @@ import type { BlobStorageClient, UploadOptions as BlobUploadOptions, } from '../blob_storage_service'; +import { getCounters, Counters } from '../usage'; import { File as FileImpl } from '../file'; import { FileShareServiceStart, InternalFileShareService } from '../file_share_service'; import { enforceMaxByteSizeTransform } from './stream_transforms'; @@ -59,6 +61,15 @@ export function createFileClient({ } export class FileClientImpl implements FileClient { + /** + * A usage counter instance that is shared across all FileClient instances. + */ + private static usageCounter: undefined | UsageCounter; + + public static configureUsageCounter(uc: UsageCounter) { + FileClientImpl.usageCounter = uc; + } + private readonly logAuditEvent: AuditLogger['log']; constructor( @@ -78,6 +89,14 @@ export class FileClientImpl implements FileClient { }; } + private getCounters() { + return getCounters(this.fileKind); + } + + private incrementUsageCounter(counter: Counters) { + FileClientImpl.usageCounter?.incrementCounter({ counterName: this.getCounters()[counter] }); + } + private instantiateFile(id: string, metadata: FileMetadata): File { return new FileImpl( id, @@ -144,19 +163,29 @@ export class FileClientImpl implements FileClient { } public async delete({ id, hasContent = true }: DeleteArgs) { - if (this.internalFileShareService) { - // Stop sharing this file - await this.internalFileShareService.deleteForFile({ id }); + this.incrementUsageCounter('DELETE'); + try { + if (this.internalFileShareService) { + // Stop sharing this file + await this.internalFileShareService.deleteForFile({ id }); + } + if (hasContent) await this.blobStorageClient.delete(id); + await this.metadataClient.delete({ id }); + this.logAuditEvent( + createAuditEvent({ + action: 'delete', + outcome: 'success', + message: `Deleted file with "${id}"`, + }) + ); + } catch (e) { + if (SavedObjectsErrorHelpers.isNotFoundError(e)) { + this.incrementUsageCounter('DELETE_ERROR_NOT_FOUND'); + } else { + this.incrementUsageCounter('DELETE_ERROR'); + } + throw e; } - if (hasContent) await this.blobStorageClient.delete(id); - await this.metadataClient.delete({ id }); - this.logAuditEvent( - createAuditEvent({ - action: 'delete', - outcome: 'success', - message: `Deleted file with "${id}"`, - }) - ); } public deleteContent: BlobStorageClient['delete'] = (arg) => { @@ -191,7 +220,13 @@ export class FileClientImpl implements FileClient { }; public download: BlobStorageClient['download'] = (args) => { - return this.blobStorageClient.download(args); + this.incrementUsageCounter('DOWNLOAD'); + try { + return this.blobStorageClient.download(args); + } catch (e) { + this.incrementUsageCounter('DOWNLOAD_ERROR'); + throw e; + } }; async share({ file, name, validUntil }: ShareArgs): Promise { diff --git a/x-pack/plugins/files/server/file_service/file_service_factory.ts b/x-pack/plugins/files/server/file_service/file_service_factory.ts index 7ced5d5e0100d..cff416e184356 100644 --- a/x-pack/plugins/files/server/file_service/file_service_factory.ts +++ b/x-pack/plugins/files/server/file_service/file_service_factory.ts @@ -13,9 +13,11 @@ import { } from '@kbn/core/server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; -import type { File, FileJSON, FileMetadata } from '../../common'; +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import { File, FileJSON, FileMetadata } from '../../common'; import { fileObjectType, fileShareObjectType, hiddenTypes } from '../saved_objects'; import { BlobStorageService } from '../blob_storage_service'; +import { FileClientImpl } from '../file_client/file_client'; import { InternalFileShareService } from '../file_share_service'; import { CreateFileArgs, @@ -26,7 +28,7 @@ import { } from './file_action_types'; import { InternalFileService } from './internal_file_service'; import { FileServiceStart } from './file_service'; -import { FileKindsRegistry } from '../file_kinds_registry'; +import { FileKindsRegistry } from '../../common/file_kinds_registry'; import { SavedObjectsFileMetadataClient } from '../file_client'; /** @@ -132,8 +134,15 @@ export class FileServiceFactoryImpl implements FileServiceFactory { /** * This function can only called during Kibana's setup phase */ - public static setup(savedObjectsSetup: SavedObjectsServiceSetup): void { + public static setup( + savedObjectsSetup: SavedObjectsServiceSetup, + usageCounter?: UsageCounter + ): void { savedObjectsSetup.registerType>(fileObjectType); savedObjectsSetup.registerType(fileShareObjectType); + if (usageCounter) { + FileClientImpl.configureUsageCounter(usageCounter); + InternalFileShareService.configureUsageCounter(usageCounter); + } } } diff --git a/x-pack/plugins/files/server/file_service/internal_file_service.ts b/x-pack/plugins/files/server/file_service/internal_file_service.ts index a7ba92db9b2e8..4e676fbc26653 100644 --- a/x-pack/plugins/files/server/file_service/internal_file_service.ts +++ b/x-pack/plugins/files/server/file_service/internal_file_service.ts @@ -12,7 +12,7 @@ import { BlobStorageService } from '../blob_storage_service'; import { InternalFileShareService } from '../file_share_service'; import { FileMetadata, File as IFile, FileKind, FileJSON, FilesMetrics } from '../../common'; import { File, toJSON } from '../file'; -import { FileKindsRegistry } from '../file_kinds_registry'; +import { FileKindsRegistry } from '../../common/file_kinds_registry'; import { FileNotFoundError } from './errors'; import type { FileMetadataClient } from '../file_client'; import type { diff --git a/x-pack/plugins/files/server/file_share_service/internal_file_share_service.ts b/x-pack/plugins/files/server/file_share_service/internal_file_share_service.ts index 6c9e09e5173b4..4d5bd95bf4b28 100644 --- a/x-pack/plugins/files/server/file_share_service/internal_file_share_service.ts +++ b/x-pack/plugins/files/server/file_share_service/internal_file_share_service.ts @@ -12,6 +12,7 @@ import { SavedObjectsErrorHelpers, } from '@kbn/core/server'; import { nodeBuilder, escapeKuery } from '@kbn/es-query'; +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import type { Pagination, FileShareJSON, @@ -22,6 +23,7 @@ import type { import { FILE_SO_TYPE } from '../../common/constants'; import type { File } from '../../common/types'; import { fileShareObjectType } from '../saved_objects'; +import { getCounters, Counters } from '../usage'; import { generateShareToken } from './generate_share_token'; import { FileShareServiceStart } from './types'; import { @@ -126,35 +128,67 @@ function validateCreateArgs({ validUntil }: CreateShareArgs): void { * @internal */ export class InternalFileShareService implements FileShareServiceStart { + private static usageCounter: undefined | UsageCounter; + + public static configureUsageCounter(uc: UsageCounter) { + InternalFileShareService.usageCounter = uc; + } + private readonly savedObjectsType = fileShareObjectType.name; constructor( private readonly savedObjects: SavedObjectsClientContract | ISavedObjectsRepository ) {} + private incrementUsageCounter(counter: Counters) { + InternalFileShareService.usageCounter?.incrementCounter({ + counterName: getCounters('share_service')[counter], + }); + } + public async share(args: CreateShareArgs): Promise { - validateCreateArgs(args); - const { file, name, validUntil } = args; - const so = await this.savedObjects.create( - this.savedObjectsType, - { - created: new Date().toISOString(), - name, - valid_until: validUntil ? validUntil : Number(moment().add(30, 'days')), - token: generateShareToken(), - }, - { - references: [{ name: file.data.name, id: file.data.id, type: FILE_SO_TYPE }], - } - ); + this.incrementUsageCounter('SHARE'); + try { + validateCreateArgs(args); + const { file, name, validUntil } = args; + const so = await this.savedObjects.create( + this.savedObjectsType, + { + created: new Date().toISOString(), + name, + valid_until: validUntil ? validUntil : Number(moment().add(30, 'days')), + token: generateShareToken(), + }, + { + references: [{ name: file.data.name, id: file.data.id, type: FILE_SO_TYPE }], + } + ); - return { ...toFileShareJSON(so), token: so.attributes.token }; + return { ...toFileShareJSON(so), token: so.attributes.token }; + } catch (e) { + if (e instanceof ExpiryDateInThePastError) { + this.incrementUsageCounter('SHARE_ERROR_EXPIRATION_IN_PAST'); + } else if (SavedObjectsErrorHelpers.isForbiddenError(e)) { + this.incrementUsageCounter('SHARE_ERROR_FORBIDDEN'); + } else if (SavedObjectsErrorHelpers.isConflictError(e)) { + this.incrementUsageCounter('SHARE_ERROR_CONFLICT'); + } else { + this.incrementUsageCounter('SHARE_ERROR'); + } + throw e; + } } public async delete({ id }: DeleteArgs): Promise { + this.incrementUsageCounter('UNSHARE'); try { await this.savedObjects.delete(this.savedObjectsType, id); } catch (e) { + if (SavedObjectsErrorHelpers.isNotFoundError(e)) { + this.incrementUsageCounter('UNSHARE_ERROR_NOT_FOUND'); + } else { + this.incrementUsageCounter('UNSHARE_ERROR'); + } if (SavedObjectsErrorHelpers.isNotFoundError(e)) { throw new FileShareNotFoundError(`File share with id "${id}" not found.`); } diff --git a/x-pack/plugins/files/server/integration_tests/file_service.test.ts b/x-pack/plugins/files/server/integration_tests/file_service.test.ts index 61196597cc5f9..6277be74e1409 100644 --- a/x-pack/plugins/files/server/integration_tests/file_service.test.ts +++ b/x-pack/plugins/files/server/integration_tests/file_service.test.ts @@ -6,7 +6,6 @@ */ import { CoreStart, ElasticsearchClient } from '@kbn/core/server'; -import { httpServiceMock } from '@kbn/core/server/mocks'; import { createTestServers, createRootWithCorePlugins, @@ -22,7 +21,7 @@ import { FileKindsRegistryImpl, getFileKindsRegistry, setFileKindsRegistry, -} from '../file_kinds_registry'; +} from '../../common/file_kinds_registry'; import { BlobStorageService } from '../blob_storage_service'; import { FileServiceStart, FileServiceFactory } from '../file_service'; import type { CreateFileArgs } from '../file_service/file_action_types'; @@ -52,7 +51,7 @@ describe('FileService', () => { coreSetup = await kbnRoot.setup(); FileServiceFactory.setup(coreSetup.savedObjects); coreStart = await kbnRoot.start(); - setFileKindsRegistry(new FileKindsRegistryImpl(httpServiceMock.createRouter())); + setFileKindsRegistry(new FileKindsRegistryImpl()); const fileKindsRegistry = getFileKindsRegistry(); fileKindsRegistry.register({ id: fileKind, diff --git a/x-pack/plugins/files/server/mocks.ts b/x-pack/plugins/files/server/mocks.ts new file mode 100644 index 0000000000000..c5b5afa5d842e --- /dev/null +++ b/x-pack/plugins/files/server/mocks.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { KibanaRequest } from '@kbn/core/server'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { FileServiceFactory, FileServiceStart } from '.'; + +export const createFileServiceMock = (): DeeplyMockedKeys => ({ + create: jest.fn(), + delete: jest.fn(), + deleteShareObject: jest.fn(), + find: jest.fn(), + getById: jest.fn(), + getByToken: jest.fn(), + getShareObject: jest.fn(), + getUsageMetrics: jest.fn(), + list: jest.fn(), + listShareObjects: jest.fn(), + update: jest.fn(), + updateShareObject: jest.fn(), +}); + +export const createFileServiceFactoryMock = (): DeeplyMockedKeys => ({ + asInternal: jest.fn(createFileServiceMock), + asScoped: jest.fn((_: KibanaRequest) => createFileServiceMock()), +}); diff --git a/x-pack/plugins/files/server/plugin.ts b/x-pack/plugins/files/server/plugin.ts index 3fa031a71141c..08357e28bd3d0 100755 --- a/x-pack/plugins/files/server/plugin.ts +++ b/x-pack/plugins/files/server/plugin.ts @@ -14,18 +14,19 @@ import type { } from '@kbn/core/server'; import { PLUGIN_ID } from '../common/constants'; - -import { BlobStorageService } from './blob_storage_service'; -import { FileServiceFactory } from './file_service'; -import type { FilesPluginSetupDependencies, FilesSetup, FilesStart } from './types'; import { setFileKindsRegistry, getFileKindsRegistry, FileKindsRegistryImpl, -} from './file_kinds_registry'; +} from '../common/file_kinds_registry'; + +import { BlobStorageService } from './blob_storage_service'; +import { FileServiceFactory } from './file_service'; +import type { FilesPluginSetupDependencies, FilesSetup, FilesStart } from './types'; + import type { FilesRequestHandlerContext, FilesRouter } from './routes/types'; -import { registerRoutes } from './routes'; -import { registerUsageCollector } from './usage'; +import { registerRoutes, registerFileKindRoutes } from './routes'; +import { Counters, registerUsageCollector } from './usage'; export class FilesPlugin implements Plugin { private readonly logger: Logger; @@ -40,7 +41,8 @@ export class FilesPlugin implements Plugin( @@ -51,6 +53,9 @@ export class FilesPlugin implements Plugin this.fileServiceFactory!.asScoped(req), asInternalUser: () => this.fileServiceFactory!.asInternal(), logger: this.logger.get('files-routes'), + usageCounter: usageCounter + ? (counter: Counters) => usageCounter.incrementCounter({ counterName: counter }) + : undefined, }, }; } @@ -58,7 +63,11 @@ export class FilesPlugin implements Plugin { + registerFileKindRoutes(router, fk); + }) + ); registerUsageCollector({ usageCollection, getFileService: () => this.fileServiceFactory?.asInternal(), diff --git a/x-pack/plugins/files/server/routes/common_schemas.ts b/x-pack/plugins/files/server/routes/common_schemas.ts index cd254c5c106c9..6f9b6a5d651a0 100644 --- a/x-pack/plugins/files/server/routes/common_schemas.ts +++ b/x-pack/plugins/files/server/routes/common_schemas.ts @@ -7,8 +7,8 @@ import { schema } from '@kbn/config-schema'; -const ALPHA_NUMERIC_WITH_SPACES_REGEX = /^[a-z0-9\s]+$/i; -const ALPHA_NUMERIC_WITH_SPACES_EXT_REGEX = /^[a-z0-9\s\.]+$/i; +const ALPHA_NUMERIC_WITH_SPACES_REGEX = /^[a-z0-9\s_]+$/i; +const ALPHA_NUMERIC_WITH_SPACES_EXT_REGEX = /^[a-z0-9\s\._]+$/i; function alphanumericValidation(v: string) { return ALPHA_NUMERIC_WITH_SPACES_REGEX.test(v) @@ -19,7 +19,7 @@ function alphanumericValidation(v: string) { function alphanumericWithExtValidation(v: string) { return ALPHA_NUMERIC_WITH_SPACES_EXT_REGEX.test(v) ? undefined - : 'Only alphanumeric characters, spaces (" ") and dots (".") are allowed'; + : 'Only alphanumeric characters, spaces (" "), dots (".") and underscores ("_") are allowed'; } export const fileName = schema.string({ diff --git a/x-pack/plugins/files/server/routes/file_kind/upload.test.ts b/x-pack/plugins/files/server/routes/file_kind/upload.test.ts new file mode 100644 index 0000000000000..59a906f5ea988 --- /dev/null +++ b/x-pack/plugins/files/server/routes/file_kind/upload.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Readable } from 'stream'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { kibanaResponseFactory } from '@kbn/core-http-router-server-internal'; + +import { FileServiceStart } from '../../file_service'; + +import { handler } from './upload'; +import { createFileKindsRequestHandlerContextMock } from '../test_utils'; +import { FileKindsRequestHandlerContext } from './types'; +import { File } from '../../file'; +import { AbortedUploadError } from '../../file/errors'; + +const createRequest = httpServerMock.createKibanaRequest; + +describe('upload', () => { + let ctx: FileKindsRequestHandlerContext; + let fileService: DeeplyMockedKeys; + + let uploadContent: jest.Mock>; + let deleteFn: jest.Mock>; + + const testErrorMessage = 'stop'; + const stopFn = async () => { + throw new Error(testErrorMessage); + }; + + beforeEach(async () => { + ({ ctx, fileService } = createFileKindsRequestHandlerContextMock()); + uploadContent = jest.fn(); + deleteFn = jest.fn(); + fileService.getById.mockResolvedValueOnce({ + id: 'test', + data: { size: 1 }, + uploadContent, + delete: deleteFn, + } as unknown as File); + }); + + it('errors as expected', async () => { + fileService.getById.mockReset(); + fileService.getById.mockImplementation(stopFn); + const { status, payload } = await handler( + ctx, + createRequest({ + params: { id: 'test' }, + query: { selfDestructOnFailure: true }, + body: Readable.from(['test']), + }), + kibanaResponseFactory + ); + expect(status).toBe(500); + expect(payload).toEqual({ message: testErrorMessage }); + expect(deleteFn).not.toHaveBeenCalled(); + }); + + describe('self-destruct on abort', () => { + it('deletes a file on failure to upload', async () => { + uploadContent.mockImplementationOnce(() => { + throw new AbortedUploadError('Request aborted'); + }); + + const { status, payload } = await handler( + ctx, + createRequest({ + params: { id: 'test' }, + query: { selfDestructOnAbort: true }, + body: Readable.from(['test']), + }), + kibanaResponseFactory + ); + expect(status).toBe(499); + expect(payload).toEqual({ message: 'Request aborted' }); + expect(deleteFn).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/files/server/routes/file_kind/upload.ts b/x-pack/plugins/files/server/routes/file_kind/upload.ts index f82eb5565af3b..9f2e5e555dcdc 100644 --- a/x-pack/plugins/files/server/routes/file_kind/upload.ts +++ b/x-pack/plugins/files/server/routes/file_kind/upload.ts @@ -21,6 +21,11 @@ export const method = 'put' as const; export const bodySchema = schema.stream(); type Body = TypeOf; +export const querySchema = schema.object({ + selfDestructOnAbort: schema.maybe(schema.boolean()), +}); +type Query = Ensure>; + export const paramsSchema = schema.object({ id: schema.string(), }); @@ -28,7 +33,7 @@ type Params = Ensure = async ( +export const handler: FileKindsRequestHandler = async ( { files, fileKind }, req, res @@ -40,6 +45,7 @@ export const handler: FileKindsRequestHandler = async ( const sub = req.events.aborted$.subscribe(abort$); const { fileService } = await files; + const { logger } = fileService; const { body: stream, params: { id }, @@ -55,7 +61,14 @@ export const handler: FileKindsRequestHandler = async ( ) { return res.badRequest({ body: { message: e.message } }); } else if (e instanceof fileErrors.AbortedUploadError) { + fileService.usageCounter?.('UPLOAD_ERROR_ABORT'); fileService.logger.error(e); + if (req.query.selfDestructOnAbort) { + logger.info( + `File (id: ${file.id}) upload aborted. Deleting file due to self-destruct flag.` + ); + file.delete(); // fire and forget + } return res.customError({ body: { message: e.message }, statusCode: 499 }); } throw e; diff --git a/x-pack/plugins/files/server/routes/index.ts b/x-pack/plugins/files/server/routes/index.ts index 5d17cb2292e4c..0a71599ac773e 100644 --- a/x-pack/plugins/files/server/routes/index.ts +++ b/x-pack/plugins/files/server/routes/index.ts @@ -11,6 +11,8 @@ import * as find from './find'; import * as metrics from './metrics'; import * as publicDownload from './public_facing/download'; +export { registerFileKindRoutes } from './file_kind'; + export function registerRoutes(router: FilesRouter) { [find, metrics, publicDownload].forEach((endpoint) => { endpoint.register(router); diff --git a/x-pack/plugins/files/server/routes/test_utils.ts b/x-pack/plugins/files/server/routes/test_utils.ts new file mode 100644 index 0000000000000..3ec4233fbcbf4 --- /dev/null +++ b/x-pack/plugins/files/server/routes/test_utils.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { createFileServiceMock } from '../mocks'; +import type { FileKindsRequestHandlerContext } from './file_kind/types'; + +export const createFileKindsRequestHandlerContextMock = ( + fileKind: string = 'test' +): { + fileService: ReturnType; + ctx: FileKindsRequestHandlerContext; +} => { + const fileService = createFileServiceMock(); + const ctx = { + fileKind, + files: Promise.resolve({ + fileService: { + asCurrentUser: () => fileService, + asInternalUser: () => fileService, + logger: loggingSystemMock.createLogger(), + }, + }), + } as unknown as FileKindsRequestHandlerContext; + + return { + ctx, + fileService, + }; +}; diff --git a/x-pack/plugins/files/server/routes/types.ts b/x-pack/plugins/files/server/routes/types.ts index eccc55e769e58..357ad0984244c 100644 --- a/x-pack/plugins/files/server/routes/types.ts +++ b/x-pack/plugins/files/server/routes/types.ts @@ -15,6 +15,7 @@ import type { Logger, } from '@kbn/core/server'; import type { FileServiceStart } from '../file_service'; +import { Counters } from '../usage'; export interface FilesRequestHandlerContext extends RequestHandlerContext { files: Promise<{ @@ -22,6 +23,7 @@ export interface FilesRequestHandlerContext extends RequestHandlerContext { asCurrentUser: () => FileServiceStart; asInternalUser: () => FileServiceStart; logger: Logger; + usageCounter?: (counter: Counters) => void; }; }>; } diff --git a/x-pack/plugins/files/server/test_utils/setup_integration_environment.ts b/x-pack/plugins/files/server/test_utils/setup_integration_environment.ts index 81d04c23bdc96..1c31649d1e8f2 100644 --- a/x-pack/plugins/files/server/test_utils/setup_integration_environment.ts +++ b/x-pack/plugins/files/server/test_utils/setup_integration_environment.ts @@ -13,7 +13,7 @@ import { } from '@kbn/core/test_helpers/kbn_server'; import pRetry from 'p-retry'; import { FileJSON } from '../../common'; -import { getFileKindsRegistry } from '../file_kinds_registry'; +import { getFileKindsRegistry } from '../../common/file_kinds_registry'; export type TestEnvironmentUtils = Awaited>; @@ -93,7 +93,7 @@ export async function setupIntegrationEnvironment() { * Register a test file type */ const testHttpConfig = { tags: ['access:myapp'] }; - getFileKindsRegistry().register({ + const myFileKind = { id: fileKind, blobStoreSettings: { esFixedSizeIndex: { index: testIndex }, @@ -107,7 +107,8 @@ export async function setupIntegrationEnvironment() { list: testHttpConfig, share: testHttpConfig, }, - }); + }; + getFileKindsRegistry().register(myFileKind); const coreStart = await root.start(); const esClient = coreStart.elasticsearch.client.asInternalUser; diff --git a/x-pack/plugins/files/server/usage/counters.ts b/x-pack/plugins/files/server/usage/counters.ts new file mode 100644 index 0000000000000..65287fe1cea26 --- /dev/null +++ b/x-pack/plugins/files/server/usage/counters.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. + */ + +export function getCounters(fileKind: string) { + return { + DELETE: `delete:${fileKind}`, + DELETE_ERROR: `delete:error:unknown:${fileKind}`, + DELETE_ERROR_NOT_FOUND: `delete:error:not_found:${fileKind}`, + + SHARE: `share:${fileKind}`, + SHARE_ERROR: `share:error:unknown:${fileKind}`, + SHARE_ERROR_EXPIRATION_IN_PAST: `share:error:expiration_in_past:${fileKind}`, + SHARE_ERROR_FORBIDDEN: `share:error:forbidden:${fileKind}`, + SHARE_ERROR_CONFLICT: `share:error:conflict:${fileKind}`, + + UNSHARE: `unshare:${fileKind}`, + UNSHARE_ERROR: `unshare:error:unknown:${fileKind}`, + UNSHARE_ERROR_NOT_FOUND: `unshare:error:not_found:${fileKind}`, + + DOWNLOAD: `download:${fileKind}`, + DOWNLOAD_ERROR: `download:error:unknown:${fileKind}`, + + UPLOAD_ERROR_ABORT: `upload:error:abort:${fileKind}`, + }; +} + +export type Counters = keyof ReturnType; diff --git a/x-pack/plugins/files/server/usage/index.ts b/x-pack/plugins/files/server/usage/index.ts index 59f577ba20b70..af624244bcdd5 100644 --- a/x-pack/plugins/files/server/usage/index.ts +++ b/x-pack/plugins/files/server/usage/index.ts @@ -6,3 +6,5 @@ */ export { registerUsageCollector } from './register_usage_collector'; +export type { Counters } from './counters'; +export { getCounters } from './counters'; diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 776691a895c17..f12cd9585851b 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -43,6 +43,12 @@ export const autoUpdatePackages = [ FLEET_SYNTHETICS_PACKAGE, ]; +export const HIDDEN_API_REFERENCE_PACKAGES = [ + FLEET_ENDPOINT_PACKAGE, + FLEET_APM_PACKAGE, + FLEET_SYNTHETICS_PACKAGE, +]; + export const autoUpgradePoliciesPackages = [FLEET_APM_PACKAGE, FLEET_SYNTHETICS_PACKAGE]; export const agentAssetTypes = { diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 138279d7b7f5a..b7bbc22d5f1eb 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -606,8 +606,17 @@ "name": "pkgVersion", "in": "path", "required": true + }, + { + "schema": { + "type": "boolean" + }, + "name": "ignoreUnverified", + "description": "Ignore if the package is fails signature verification", + "in": "query" } ], + "required": true, "post": { "summary": "Packages - Install", "tags": [], diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 60c305097c4cc..fab23c6596a8f 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -376,6 +376,12 @@ paths: name: pkgVersion in: path required: true + - schema: + type: boolean + name: ignoreUnverified + description: Ignore if the package is fails signature verification + in: query + required: true post: summary: Packages - Install tags: [] diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml index 8164d04fc98f1..b0ef55cb7e52d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml @@ -47,6 +47,11 @@ parameters: name: pkgVersion in: path required: true + - schema: + type: boolean + name: ignoreUnverified + description: 'Ignore if the package is fails signature verification' + in: query post: summary: Packages - Install tags: [] diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index fb1a1c9965780..b36e64bc901d4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -13,6 +13,8 @@ import type { FieldSpec } from '@kbn/data-plugin/common'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; + import { useStartServices } from '../hooks'; import { INDEX_NAME, AGENTS_PREFIX } from '../constants'; @@ -35,7 +37,17 @@ export const SearchBar: React.FunctionComponent = ({ indexPattern = INDEX_NAME, dataTestSubj, }) => { - const { data } = useStartServices(); + const { + data, + unifiedSearch, + storage, + notifications, + http, + docLinks, + uiSettings, + usageCollection, + } = useStartServices(); + const [indexPatternFields, setIndexPatternFields] = useState(); const isQueryValid = useMemo(() => { @@ -105,6 +117,17 @@ export const SearchBar: React.FunctionComponent = ({ submitOnBlur isClearable autoSubmit + appName={i18n.translate('xpack.fleet.appTitle', { defaultMessage: 'Fleet' })} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} {...(dataTestSubj && { dataTestSubj })} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index cc49d03f1f1c6..f517d4092942e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -27,7 +27,11 @@ import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks'; import type { CreatePackagePolicyRequest } from '../../../../../../../common/types'; import { splitPkgKey } from '../../../../../../../common/services'; -import { dataTypes, FLEET_SYSTEM_PACKAGE } from '../../../../../../../common/constants'; +import { + dataTypes, + FLEET_SYSTEM_PACKAGE, + HIDDEN_API_REFERENCE_PACKAGES, +} from '../../../../../../../common/constants'; import { useConfirmForceInstall } from '../../../../../integrations/hooks'; import type { AgentPolicy, @@ -557,7 +561,13 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ }, ]; - const { showDevtoolsRequest } = ExperimentalFeaturesService.get(); + const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = + ExperimentalFeaturesService.get(); + + const showDevtoolsRequest = + !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && + isShowDevtoolRequestExperimentEnabled; + const devtoolRequest = useMemo( () => generateCreatePackagePolicyDevToolsRequest({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index c0eeb7044c1cf..e11d1ccda3b91 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -63,6 +63,7 @@ import type { GetOnePackagePolicyResponse, UpgradePackagePolicyDryRunResponse, } from '../../../../../../common/types/rest_spec'; +import { HIDDEN_API_REFERENCE_PACKAGES } from '../../../../../../common/constants'; import type { PackagePolicyEditExtensionComponentProps } from '../../../types'; import { ExperimentalFeaturesService, pkgKeyFromPackageInfo } from '../../../services'; import { generateUpdatePackagePolicyDevToolsRequest } from '../services'; @@ -577,7 +578,12 @@ export const EditPackagePolicyForm = memo<{ ] ); - const { showDevtoolsRequest } = ExperimentalFeaturesService.get(); + const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = + ExperimentalFeaturesService.get(); + + const showDevtoolsRequest = + !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && + isShowDevtoolRequestExperimentEnabled; const devtoolRequest = useMemo( () => diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx index 0fdcc06c47e18..166387780f8cc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx @@ -28,7 +28,8 @@ export const LogQueryBar: React.FunctionComponent<{ isQueryValid: boolean; onUpdateQuery: (query: string, runQuery?: boolean) => void; }> = memo(({ query, isQueryValid, onUpdateQuery }) => { - const { data } = useStartServices(); + const { data, notifications, http, docLinks, uiSettings, unifiedSearch, storage } = + useStartServices(); const [indexPatternFields, setIndexPatternFields] = useState(); useEffect(() => { @@ -79,6 +80,8 @@ export const LogQueryBar: React.FunctionComponent<{ onSubmit={(newQuery) => { onUpdateQuery(newQuery.query as string, true); }} + appName={i18n.translate('xpack.fleet.appTitle', { defaultMessage: 'Fleet' })} + deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, storage }} /> ); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx index 74a418bebde77..98b1688308203 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx @@ -21,6 +21,11 @@ jest.mock('../../../../integrations/hooks/use_confirm_force_install', () => ({ })); jest.mock('../../../hooks', () => ({ ...jest.requireActual('../../../hooks'), + UIExtensionsContext: { + Provider: (props: any) => { + return props.children; + }, + }, sendGetAgents: jest.fn(), useGetAgentPolicies: jest.fn().mockReturnValue({ data: { items: [{ id: 'policy1' }] }, diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/confirm_open_unverified_modal.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/confirm_open_unverified_modal.tsx new file mode 100644 index 0000000000000..a11556d81aec4 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/components/confirm_open_unverified_modal.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiConfirmModal, EuiCallOut, EuiLink } from '@elastic/eui'; +import type { DocLinksStart } from '@kbn/core/public'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const ConfirmOpenUnverifiedModal: React.FC<{ + onCancel: () => void; + onConfirm: () => void; + pkgName: string; + docLinks: DocLinksStart; +}> = ({ onCancel, onConfirm, pkgName, docLinks }) => { + return ( + + + + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="danger" + data-test-subj="ConfirmOpenUnverifiedModal" + > + + + + ), + }} + /> + } + /> + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts index 19fe6c0467b22..5b6b19af169f0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts @@ -13,3 +13,4 @@ export * from './use_package_install'; export * from './use_agent_policy_context'; export * from './use_integrations_state'; export * from './use_confirm_force_install'; +export * from './use_confirm_open_unverified'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_force_install.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_force_install.tsx index 94c6e7ddd5541..1adacb5fe733d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_force_install.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_force_install.tsx @@ -8,7 +8,7 @@ import type { DocLinksStart, OverlayStart } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import React from 'react'; +import React, { useCallback } from 'react'; import { useStartServices } from '../../fleet/hooks'; import { ConfirmForceInstallModal } from '../components'; @@ -44,6 +44,8 @@ const confirmForceInstall = ({ export const useConfirmForceInstall = () => { const { overlays, docLinks } = useStartServices(); - return (pkg: { name: string; version: string }) => - confirmForceInstall({ pkg, overlays, docLinks }); + return useCallback( + (pkg: { name: string; version: string }) => confirmForceInstall({ pkg, overlays, docLinks }), + [docLinks, overlays] + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_open_unverified.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_open_unverified.tsx new file mode 100644 index 0000000000000..1d36a13d319a8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_confirm_open_unverified.tsx @@ -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 type { DocLinksStart, OverlayStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; + +import React, { useCallback } from 'react'; + +// Direct imports are important here, importing all hooks breaks unit tests +// and increases bundle size because this is imported on first page load +import { useStartServices } from '../../../hooks/use_core'; +import { ConfirmOpenUnverifiedModal } from '../components/confirm_open_unverified_modal'; + +const confirmOpenUnverified = ({ + pkgName, + overlays, + docLinks, +}: { + pkgName: string; + overlays: OverlayStart; + docLinks: DocLinksStart; +}): Promise => + new Promise((resolve) => { + const session = overlays.openModal( + toMountPoint( + { + session.close(); + resolve(true); + }} + onCancel={() => { + session.close(); + resolve(false); + }} + docLinks={docLinks} + /> + ) + ); + }); + +export const useConfirmOpenUnverified = () => { + const { overlays, docLinks } = useStartServices(); + + return useCallback( + (pkgName: string) => confirmOpenUnverified({ pkgName, overlays, docLinks }), + [docLinks, overlays] + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index c55adfe4768d1..d43afbb28835c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -25,6 +25,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import semverLt from 'semver/functions/lt'; import { splitPkgKey } from '../../../../../../../common/services'; +import { HIDDEN_API_REFERENCE_PACKAGES } from '../../../../../../../common/constants'; + import { useGetPackageInstallStatus, useSetPackageInstallStatus, @@ -490,21 +492,23 @@ export function Detail() { }); } - tabs.push({ - id: 'api-reference', - name: ( - - ), - isSelected: panel === 'api-reference', - 'data-test-subj': `tab-api-reference`, - href: getHref('integration_details_api_reference', { - pkgkey: packageInfoKey, - ...(integration ? { integration } : {}), - }), - }); + if (!HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo.name)) { + tabs.push({ + id: 'api-reference', + name: ( + + ), + isSelected: panel === 'api-reference', + 'data-test-subj': `tab-api-reference`, + href: getHref('integration_details_api_reference', { + pkgkey: packageInfoKey, + ...(integration ? { integration } : {}), + }), + }); + } return tabs; }, [ diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts index 22458b998d3ed..371edf0c6f6e9 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts @@ -17,9 +17,15 @@ jest.mock('../../hooks/use_request', () => { const module = jest.requireActual('../../hooks/use_request'); return { ...module, - useGetSettings: jest.fn(), - sendGetOneAgentPolicy: jest.fn(), - useGetAgents: jest.fn(), + useGetSettings: jest.fn().mockReturnValue({ + data: { item: { fleet_server_hosts: ['test'] } }, + }), + sendGetOneAgentPolicy: jest.fn().mockResolvedValue({ + data: { item: { package_policies: [] } }, + }), + useGetAgents: jest.fn().mockReturnValue({ + data: { items: [{ policy_id: 'fleet-server-policy' }] }, + }), useGetAgentPolicies: jest.fn(), }; }); diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 3448542bf7df1..57b2b93fabb25 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -7,7 +7,9 @@ import useAsync from 'react-use/lib/useAsync'; -import { epmRouteService } from '../../services'; +import { useEffect, useState } from 'react'; + +import { epmRouteService, isVerificationError } from '../../services'; import type { GetCategoriesRequest, GetCategoriesResponse, @@ -24,6 +26,8 @@ import type { FleetErrorResponse, GetStatsResponse } from '../../../common/types import { getCustomIntegrations } from '../../services/custom_integrations'; +import { useConfirmOpenUnverified } from '../../applications/integrations/hooks/use_confirm_open_unverified'; + import { useRequest, sendRequest } from './use_request'; export function useGetAppendCustomIntegrations() { @@ -67,11 +71,34 @@ export const useGetLimitedPackages = () => { }); }; -export const useGetPackageInfoByKey = (pkgName: string, pkgVersion?: string) => { - return useRequest({ +export const useGetPackageInfoByKey = ( + pkgName: string, + pkgVersion?: string, + ignoreUnverified: boolean = false +) => { + const confirmOpenUnverified = useConfirmOpenUnverified(); + const [ignoreUnverifiedQueryParam, setIgnoreUnverifiedQueryParam] = useState(ignoreUnverified); + const res = useRequest({ path: epmRouteService.getInfoPath(pkgName, pkgVersion), method: 'get', + query: ignoreUnverifiedQueryParam ? { ignoreUnverified: ignoreUnverifiedQueryParam } : {}, }); + + useEffect(() => { + const confirm = async () => { + const forceInstall = await confirmOpenUnverified(pkgName); + + if (forceInstall) { + setIgnoreUnverifiedQueryParam(true); + } + }; + + if (res.error && isVerificationError(res.error)) { + confirm(); + } + }, [res.error, pkgName, pkgVersion, confirmOpenUnverified]); + + return res; }; export const useGetPackageStats = (pkgName: string) => { @@ -81,10 +108,15 @@ export const useGetPackageStats = (pkgName: string) => { }); }; -export const sendGetPackageInfoByKey = (pkgName: string, pkgVersion?: string) => { +export const sendGetPackageInfoByKey = ( + pkgName: string, + pkgVersion?: string, + ignoreUnverified?: boolean +) => { return sendRequest({ path: epmRouteService.getInfoPath(pkgName, pkgVersion), method: 'get', + query: ignoreUnverified ? { ignoreUnverified } : {}, }); }; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index ad7b3b02a9aa5..5b5a31461c911 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -29,7 +29,10 @@ import { once } from 'lodash'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; -import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '@kbn/core/public'; @@ -97,6 +100,7 @@ export interface FleetStartDeps { customIntegrations: CustomIntegrationsStart; share: SharePluginStart; cloud?: CloudStart; + usageCollection?: UsageCollectionStart; } export interface FleetStartServices extends CoreStart, Exclude { diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index e5e2cc5128074..242e272cd184b 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -194,11 +194,13 @@ export const getFileHandler: FleetRequestHandler< }; export const getInfoHandler: FleetRequestHandler< - TypeOf + TypeOf, + TypeOf > = async (context, request, response) => { try { const savedObjectsClient = (await context.fleet).epm.internalSoClient; const { pkgName, pkgVersion } = request.params; + const { ignoreUnverified = false } = request.query; if (pkgVersion && !semverValid(pkgVersion)) { throw new FleetError('Package version is not a valid semver'); } @@ -207,6 +209,7 @@ export const getInfoHandler: FleetRequestHandler< pkgName, pkgVersion: pkgVersion || '', skipArchive: true, + ignoreUnverified, }); const body: GetInfoResponse = { item: res, diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 2b4ebbb5728f2..765c6c649db25 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -203,6 +203,7 @@ export const createPackagePolicyHandler: FleetRequestHandler< savedObjectsClient: soClient, pkgName: pkg.name, pkgVersion: pkg.version, + ignoreUnverified: force, }); newPackagePolicy = simplifiedPackagePolicytoNewPackagePolicy(newPolicy, pkgInfo); } else { 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 c333ba9f7bc3d..b0aaa64a69239 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -125,12 +125,14 @@ export async function getPackageInfo({ pkgName, pkgVersion, skipArchive = false, + ignoreUnverified = false, }: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; pkgVersion: string; /** Avoid loading the registry archive into the cache (only use for performance reasons). Defaults to `false` */ skipArchive?: boolean; + ignoreUnverified?: boolean; }): Promise { const [savedObject, latestPackage] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), @@ -169,6 +171,7 @@ export async function getPackageInfo({ savedObjectsClient, installedPkg: savedObject?.attributes, getPkgInfoFromArchive: packageInfo?.type === 'input', + ignoreUnverified, })); } @@ -239,6 +242,7 @@ export async function getPackageFromSource(options: { installedPkg?: Installation; savedObjectsClient: SavedObjectsClientContract; getPkgInfoFromArchive?: boolean; + ignoreUnverified?: boolean; }): Promise { const logger = appContextService.getLogger(); const { @@ -247,6 +251,7 @@ export async function getPackageFromSource(options: { installedPkg, savedObjectsClient, getPkgInfoFromArchive = true, + ignoreUnverified = false, } = options; let res: GetPackageResponse; @@ -290,7 +295,10 @@ export async function getPackageFromSource(options: { } } else { // else package is not installed or installed and missing from cache and storage and installed from registry - res = await Registry.getRegistryPackage(pkgName, pkgVersion, { getPkgInfoFromArchive }); + res = await Registry.getRegistryPackage(pkgName, pkgVersion, { + getPkgInfoFromArchive, + ignoreUnverified, + }); logger.debug(`retrieved uninstalled package ${pkgName}-${pkgVersion} from registry`); } if (!res) { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 4b4b9615009f3..77c5b67d9a782 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -566,59 +566,56 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const packageInfos = await getPackageInfoForPackagePolicies(packagePolicyUpdates, soClient); await soClient.bulkUpdate( - await pMap( - packagePolicyUpdates, - async (packagePolicyUpdate) => { - const id = packagePolicyUpdate.id; - const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() }; - const oldPackagePolicy = oldPackagePolicies.find((p) => p.id === id); - if (!oldPackagePolicy) { - throw new Error('Package policy not found'); - } + await pMap(packagePolicyUpdates, async (packagePolicyUpdate) => { + const id = packagePolicyUpdate.id; + const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() }; + const oldPackagePolicy = oldPackagePolicies.find((p) => p.id === id); + if (!oldPackagePolicy) { + throw new Error('Package policy not found'); + } - const { version, ...restOfPackagePolicy } = packagePolicy; + // id and version are not part of the saved object attributes + const { version, id: _id, ...restOfPackagePolicy } = packagePolicy; - if (packagePolicyUpdate.is_managed && !options?.force) { - throw new PackagePolicyRestrictionRelatedError(`Cannot update package policy ${id}`); - } + if (packagePolicyUpdate.is_managed && !options?.force) { + throw new PackagePolicyRestrictionRelatedError(`Cannot update package policy ${id}`); + } - let inputs = restOfPackagePolicy.inputs.map((input) => - assignStreamIdToInput(oldPackagePolicy.id, input) + let inputs = restOfPackagePolicy.inputs.map((input) => + assignStreamIdToInput(oldPackagePolicy.id, input) + ); + + inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs, options?.force); + let elasticsearch: PackagePolicy['elasticsearch']; + if (packagePolicy.package?.name) { + const pkgInfo = packageInfos.get( + `${packagePolicy.package.name}-${packagePolicy.package.version}` ); + if (pkgInfo) { + validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs, options?.force); - let elasticsearch: PackagePolicy['elasticsearch']; - if (packagePolicy.package?.name) { - const pkgInfo = packageInfos.get( - `${packagePolicy.package.name}-${packagePolicy.package.version}` - ); - if (pkgInfo) { - validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - - inputs = await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs); - elasticsearch = pkgInfo.elasticsearch; - } + inputs = await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs); + elasticsearch = pkgInfo.elasticsearch; } + } - // Handle component template/mappings updates for experimental features, e.g. synthetic source - await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); + // Handle component template/mappings updates for experimental features, e.g. synthetic source + await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); - return { - type: SAVED_OBJECT_TYPE, - id, - attributes: { - ...restOfPackagePolicy, - inputs, - elasticsearch, - revision: oldPackagePolicy.revision + 1, - updated_at: new Date().toISOString(), - updated_by: options?.user?.username ?? 'system', - }, - version, - }; - }, - { concurrency: 50 } - ) + return { + type: SAVED_OBJECT_TYPE, + id, + attributes: { + ...restOfPackagePolicy, + inputs, + elasticsearch, + revision: oldPackagePolicy.revision + 1, + updated_at: new Date().toISOString(), + updated_by: options?.user?.username ?? 'system', + }, + version, + }; + }) ); const agentPolicyIds = new Set(packagePolicyUpdates.map((p) => p.policy_id)); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index 1385c110f2e4e..f69576a2a8b56 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -35,6 +35,9 @@ export const GetInfoRequestSchema = { pkgName: schema.string(), pkgVersion: schema.maybe(schema.string()), }), + query: schema.object({ + ignoreUnverified: schema.maybe(schema.boolean()), + }), }; export const GetInfoRequestSchemaDeprecated = { diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index 3ea4d3cdca356..641e595893c0a 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -8,7 +8,8 @@ "licensing", "data", "navigation", - "savedObjects" + "savedObjects", + "unifiedSearch" ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index d3ba129dbb316..1976b12621f4f 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -22,6 +22,7 @@ import { import ReactDOM from 'react-dom'; import React from 'react'; import { DataPlugin, DataViewsContract } from '@kbn/data-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; @@ -56,6 +57,7 @@ export interface GraphDependencies { toastNotifications: ToastsStart; indexPatterns: DataViewsContract; data: ReturnType; + unifiedSearch: UnifiedSearchPublicPluginStart; savedObjectsClient: SavedObjectsClientContract; addBasePath: (url: string) => string; getBasePath: () => string; diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index 64c85bc06750d..051c0fa66ab89 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -37,6 +37,7 @@ export const WorkspaceRoute = ({ capabilities, storage, data, + unifiedSearch, getBasePath, addBasePath, setHeaderActionMenu, @@ -73,9 +74,10 @@ export const WorkspaceRoute = ({ appName: 'graph', storage, data, + unifiedSearch, ...coreStart, }), - [coreStart, data, storage] + [coreStart, data, storage, unifiedSearch] ); const [store] = useState(() => diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index 9aa05f64754cd..68b7ad7b579b1 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -97,7 +97,17 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) const kibana = useKibana(); const { services, overlays } = kibana; - const { savedObjects, uiSettings } = services; + const { + savedObjects, + uiSettings, + appName, + unifiedSearch, + data, + storage, + notifications, + http, + docLinks, + } = services; if (!overlays) return null; return ( @@ -161,6 +171,16 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) })} query={query} onChange={setQuery} + appName={appName} + deps={{ + unifiedSearch, + data, + storage, + notifications, + http, + docLinks, + uiSettings, + }} /> diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 0d9fac79600ad..d4f8471426cc5 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -25,6 +25,7 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; import { checkLicense } from '../common/check_license'; import { ConfigSchema } from '../config'; @@ -37,6 +38,7 @@ export interface GraphPluginStartDependencies { navigation: NavigationStart; licensing: LicensingPluginStart; data: DataPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; savedObjects: SavedObjectsStart; home?: HomePublicPluginStart; spaces?: SpacesApi; @@ -93,6 +95,7 @@ export class GraphPlugin coreStart, navigation: pluginsStart.navigation, data: pluginsStart.data, + unifiedSearch: pluginsStart.unifiedSearch, savedObjectsClient: coreStart.savedObjects.client, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index 2209e1810a788..2204dd16fd46a 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -364,7 +364,7 @@ export const aggregationType: { [key: string]: any } = { defaultMessage: 'Average', }), fieldRequired: true, - validNormalizedTypes: ['number'], + validNormalizedTypes: ['number', 'histogram'], value: AGGREGATION_TYPES.AVERAGE, }, max: { @@ -372,7 +372,7 @@ export const aggregationType: { [key: string]: any } = { defaultMessage: 'Max', }), fieldRequired: true, - validNormalizedTypes: ['number', 'date'], + validNormalizedTypes: ['number', 'date', 'histogram'], value: AGGREGATION_TYPES.MAX, }, min: { @@ -380,7 +380,7 @@ export const aggregationType: { [key: string]: any } = { defaultMessage: 'Min', }), fieldRequired: true, - validNormalizedTypes: ['number', 'date'], + validNormalizedTypes: ['number', 'date', 'histogram'], value: AGGREGATION_TYPES.MIN, }, cardinality: { @@ -413,7 +413,7 @@ export const aggregationType: { [key: string]: any } = { }), fieldRequired: false, value: AGGREGATION_TYPES.SUM, - validNormalizedTypes: ['number'], + validNormalizedTypes: ['number', 'histogram'], }, p95: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.p95', { @@ -421,7 +421,7 @@ export const aggregationType: { [key: string]: any } = { }), fieldRequired: false, value: AGGREGATION_TYPES.P95, - validNormalizedTypes: ['number'], + validNormalizedTypes: ['number', 'histogram'], }, p99: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.p99', { @@ -429,6 +429,6 @@ export const aggregationType: { [key: string]: any } = { }), fieldRequired: false, value: AGGREGATION_TYPES.P99, - validNormalizedTypes: ['number'], + validNormalizedTypes: ['number', 'histogram'], }, }; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 6076849b79f12..440edcbdd6c0e 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { LogCustomizationMenu } from '../../../components/logging/log_customization_menu'; import { LogDatepicker } from '../../../components/logging/log_datepicker'; import { LogHighlightsMenu } from '../../../components/logging/log_highlights_menu'; @@ -31,6 +32,8 @@ export const LogsToolbar = () => { const { filterQueryDraft, isFilterQueryDraftValid, applyLogFilterQuery, setLogFilterQueryDraft } = useLogFilterStateContext(); const { setSurroundingLogsId } = useLogEntryFlyoutContext(); + const { http, notifications, docLinks, uiSettings, data, storage, unifiedSearch } = + useKibanaContextForPlugin().services; const { setHighlightTerms, @@ -71,6 +74,10 @@ export const LogsToolbar = () => { defaultMessage: 'Search for log entries… (e.g. host.name:host-1)', })} query={filterQueryDraft} + appName={i18n.translate('xpack.infra.appName', { + defaultMessage: 'Infra logs', + })} + deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, storage }} /> diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index a87d7def58e60..461ed1261233a 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -29,6 +29,7 @@ import type { // import type { OsqueryPluginStart } from '../../osquery/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { UnwrapPromise } from '../common/utility_types'; import type { SourceProviderProps, @@ -75,6 +76,7 @@ export interface InfraClientStartDeps { osquery?: unknown; // OsqueryPluginStart; share: SharePluginStart; lens: LensPublicStart; + storage: IStorageWrapper; } export type InfraClientCoreSetup = CoreSetup; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 6aa7bef84d3e2..7cf1331282d4e 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -68,6 +68,7 @@ export async function getLensServices( fieldFormats, spaces, discover, + unifiedSearch, } = startDependencies; const storage = new Storage(localStorage); @@ -108,6 +109,8 @@ export async function getLensServices( dashboardFeatureFlag: startDependencies.dashboard.dashboardFeatureFlagConfig, spaces, discover, + unifiedSearch, + docLinks: coreStart.docLinks, }; } diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index bc7cf11914b83..b2466c60e6e4e 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -42,6 +42,8 @@ import type { EmbeddableEditorState, EmbeddableStateTransfer } from '@kbn/embedd import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { DocLinksStart } from '@kbn/core-doc-links-browser'; import type { DatasourceMap, EditorFrameInstance, @@ -152,7 +154,8 @@ export interface LensAppServices { spaces: SpacesApi; charts: ChartsPluginSetup; discover?: DiscoverStart; - + unifiedSearch: UnifiedSearchPublicPluginStart; + docLinks: DocLinksStart; // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag: DashboardFeatureFlagConfig; dataViewEditor: DataViewEditorStart; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index fad4a6e3924b8..e4b634d02207f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -79,6 +79,7 @@ import { selectChangesApplied, VisualizationState, DatasourceStates, + DataViewsState, } from '../../../state_management'; import type { LensInspector } from '../../../lens_inspector_service'; import { inferTimeField, DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; @@ -178,17 +179,20 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ visualization: VisualizationState; visualizationMap: VisualizationMap; datasourceLayers: DatasourceLayers; + dataViews: DataViewsState; }>(); + const { dataViews } = framePublicAPI; + renderDeps.current = { datasourceMap, datasourceStates, visualization, visualizationMap, datasourceLayers, + dataViews, }; - const { dataViews } = framePublicAPI; const onRender$ = useCallback(() => { if (renderDeps.current) { const datasourceEvents = Object.values(renderDeps.current.datasourceMap).reduce( @@ -210,8 +214,16 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ renderDeps.current.visualization.activeId ].getRenderEventCounters?.(renderDeps.current.visualization.state) ?? []; } + const events = ['vis_editor', ...datasourceEvents, ...visualizationEvents]; + + const adHocDataViews = Object.values(renderDeps.current.dataViews.indexPatterns || {}).filter( + (indexPattern) => !indexPattern.isPersisted + ); + adHocDataViews.forEach(() => { + events.push('ad_hoc_data_view'); + }); - trackUiCounterEvents(['vis_editor', ...datasourceEvents, ...visualizationEvents]); + trackUiCounterEvents(events); } }, []); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index fb7d7646871c7..28e987d6dbf4e 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -563,10 +563,18 @@ export class Embeddable const executionContext = this.getExecutionContext(); - trackUiCounterEvents( - [...datasourceEvents, ...visualizationEvents, ...getExecutionContextEvents(executionContext)], - executionContext - ); + const events = [ + ...datasourceEvents, + ...visualizationEvents, + ...getExecutionContextEvents(executionContext), + ]; + + const adHocDataViews = Object.values(this.savedVis?.state.adHocDataViews || {}); + adHocDataViews.forEach(() => { + events.push('ad_hoc_data_view'); + }); + + trackUiCounterEvents(events, executionContext); this.renderComplete.dispatchComplete(); this.updateOutput({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index bca5a811adada..fc05ff3d9f254 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -1039,6 +1039,42 @@ describe('IndexPattern Data Source', () => { `); }); + it('should not add time shift to nested count metric', async () => { + const queryBaseState: IndexPatternPrivateState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + timeShift: '1h', + reducedTimeRange: '1m', + }, + }, + }, + }, + }; + + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; + const filteredMetricAgg = (ast.chain[1].arguments.aggs[0] as Ast).chain[0].arguments; + const metricAgg = (filteredMetricAgg.customMetric[0] as Ast).chain[0].arguments; + const bucketAgg = (filteredMetricAgg.customBucket[0] as Ast).chain[0].arguments; + expect(filteredMetricAgg.timeShift[0]).toEqual('1h'); + expect(bucketAgg.timeWindow[0]).toEqual('1m'); + expect(metricAgg.timeWindow).toEqual(undefined); + expect(metricAgg.timeShift).toEqual(undefined); + }); + it('should put column formatters after calculated columns', async () => { const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 7c67196c1dff5..20d0df1358be7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -281,6 +281,7 @@ export function getIndexPatternDatasource({ dataViews, fieldFormats, charts, + unifiedSearch, }} > { const { inputValue, handleInputChange } = useDebouncedValue({ value, onChange }); + const lensAppServices = useKibana().services; + + const { data, uiSettings, http, notifications, docLinks, storage, unifiedSearch } = + lensAppServices; return ( ); }; diff --git a/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts b/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts index f125a9e830ec9..e52c3d312802b 100644 --- a/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts +++ b/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts @@ -12,10 +12,10 @@ import { setToggleFullscreen } from './lens_slice'; /** cancels updates to the store that don't change the state */ export const fullscreenMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAPI) => { return (next: Dispatch) => (action: Action) => { - next(action); if (setToggleFullscreen.match(action)) { const isFullscreen = (store.getState as LensGetState)().lens.isFullscreenDatasource; - storeDeps.lensServices.chrome.setIsVisible(!isFullscreen); + storeDeps.lensServices.chrome.setIsVisible(Boolean(isFullscreen)); } + next(action); }; }; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts index 941352c8f8520..e17b0d7eff412 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -41,6 +41,8 @@ interface StartPlugins { charts: ChartsPluginStart; cases?: CasesUiStart; unifiedSearch: UnifiedSearchPublicPluginStart; + core: CoreStart; + appName: string; } export type StartServices = CoreStart & StartPlugins & { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index 79e7403cd3513..33ab5e72b9fd1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -23,6 +23,7 @@ import { } from '../../../../../../../common/constants/search'; import { removeFilterFromQueryString } from '../../../../../explorer/explorer_utils'; import { SavedSearchQuery } from '../../../../../contexts/ml'; +import { useMlKibana } from '../../../../../contexts/kibana'; interface ErrorMessage { query: string; @@ -56,6 +57,10 @@ export const ExplorationQueryBar: FC = ({ const [idToSelectedMap, setIdToSelectedMap] = useState<{ [id: string]: boolean }>({}); const [errorMessage, setErrorMessage] = useState(undefined); + const { services } = useMlKibana(); + const { unifiedSearch, data, storage, appName, notifications, http, docLinks, uiSettings } = + services; + const searchChangeHandler = (q: Query) => setSearchInput(q); const regex = useMemo( @@ -197,6 +202,8 @@ export const ExplorationQueryBar: FC = ({ disableAutoFocus={true} dataTestSubj="mlDFAnalyticsQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" + appName={appName} + deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, storage }} /> {filters && filters.options && ( diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index a64625959e1b5..90afeb2efb3b1 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -15,6 +15,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { SEARCH_QUERY_LANGUAGE, ErrorMessage } from '../../../../../common/constants/search'; import { InfluencersFilterQuery } from '../../../../../common/types/es_client'; import { useAnomalyExplorerContext } from '../../anomaly_explorer_context'; +import { useMlKibana } from '../../../contexts/kibana'; export const DEFAULT_QUERY_LANG = SEARCH_QUERY_LANGUAGE.KUERY; @@ -111,6 +112,9 @@ export const ExplorerQueryBar: FC = ({ updateLanguage, }) => { const { anomalyExplorerCommonStateService } = useAnomalyExplorerContext(); + const { services } = useMlKibana(); + const { unifiedSearch, data, storage, appName, notifications, http, docLinks, uiSettings } = + services; // The internal state of the input query bar updated on every key stroke. const [searchInput, setSearchInput] = useState( @@ -166,6 +170,8 @@ export const ExplorerQueryBar: FC = ({ disableAutoFocus dataTestSubj="explorerQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" + appName={appName} + deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, storage }} /> } isOpen={errorMessage?.query === searchInput.query && errorMessage?.message !== ''} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_anomalies_container.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_anomalies_container.tsx index 960564fa5dfbf..542c71fa9fd4f 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_anomalies_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_anomalies_container.tsx @@ -34,6 +34,7 @@ interface ExplorerAnomaliesContainerProps { onSelectEntity: (fieldName: string, fieldValue: string, operation: EntityFieldOperation) => void; showSelectedInterval?: boolean; chartsService: ChartsPluginStart; + timeRange: { from: string; to: string } | undefined; } const tooManyBucketsCalloutMsg = i18n.translate( @@ -56,6 +57,7 @@ export const ExplorerAnomaliesContainer: FC = ( onSelectEntity, showSelectedInterval, chartsService, + timeRange, }) => { return ( <> @@ -87,6 +89,7 @@ export const ExplorerAnomaliesContainer: FC = ( mlLocator, timeBuckets, timefilter, + timeRange, onSelectEntity, tooManyBucketsCalloutMsg, showSelectedInterval, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index d000b5cd465ef..30a32f1953a16 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -94,6 +94,7 @@ function ExplorerChartContainer({ mlLocator, timeBuckets, timefilter, + timeRange, onSelectEntity, recentlyAccessed, tooManyBucketsCalloutMsg, @@ -105,7 +106,6 @@ function ExplorerChartContainer({ const { services: { - data, share, application: { navigateToApp }, }, @@ -118,20 +118,35 @@ function ExplorerChartContainer({ const locator = share.url.locators.get(MAPS_APP_LOCATOR); const location = await locator.getLocation({ initialLayers: initialLayers, - timeRange: data.query.timefilter.timefilter.getTime(), + timeRange: timeRange ?? timefilter?.getTime(), ...(queryString !== undefined ? { query } : {}), }); return location; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [series?.jobId]); + }, [series?.jobId, timeRange]); useEffect(() => { let isCancelled = false; const generateLink = async () => { + // Prioritize timeRange from embeddable panel or case + // Else use the time range from data plugins's timefilters service + let mergedTimeRange = timeRange; + const bounds = timefilter?.getActiveBounds(); + if (!timeRange && bounds) { + mergedTimeRange = { + from: bounds.min.toISOString(), + to: bounds.max.toISOString(), + }; + } + if (!isCancelled && series.functionDescription !== ML_JOB_AGGREGATION.LAT_LONG) { try { - const singleMetricViewerLink = await getExploreSeriesLink(mlLocator, series, timefilter); + const singleMetricViewerLink = await getExploreSeriesLink( + mlLocator, + series, + mergedTimeRange + ); setExplorerSeriesLink(singleMetricViewerLink); } catch (error) { setExplorerSeriesLink(''); @@ -143,7 +158,7 @@ function ExplorerChartContainer({ isCancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mlLocator, series]); + }, [mlLocator, series, timeRange]); useEffect( function getMapsPluginLink() { @@ -358,6 +373,7 @@ export const ExplorerChartsContainerUI = ({ mlLocator, timeBuckets, timefilter, + timeRange, onSelectEntity, tooManyBucketsCalloutMsg, showSelectedInterval, @@ -420,6 +436,7 @@ export const ExplorerChartsContainerUI = ({ mlLocator={mlLocator} timeBuckets={timeBuckets} timefilter={timefilter} + timeRange={timeRange} onSelectEntity={onSelectEntity} recentlyAccessed={recentlyAccessed} tooManyBucketsCalloutMsg={tooManyBucketsCalloutMsg} diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index b05c8b20b22ca..4936f80f1911c 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -174,12 +174,9 @@ export function getChartType(config) { return chartType; } -export async function getExploreSeriesLink(mlLocator, series, timefilter) { +export async function getExploreSeriesLink(mlLocator, series, timeRange) { // Open the Single Metric dashboard over the same overall bounds and // zoomed in to the same time as the current chart. - const bounds = timefilter.getActiveBounds(); - const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z - const to = bounds.max.toISOString(); const zoomFrom = moment(series.plotEarliest).toISOString(); const zoomTo = moment(series.plotLatest).toISOString(); @@ -206,11 +203,7 @@ export async function getExploreSeriesLink(mlLocator, series, timefilter) { pause: true, value: 0, }, - timeRange: { - from: from, - to: to, - mode: 'absolute', - }, + timeRange, zoom: { from: zoomFrom, to: zoomTo, diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/__snapshots__/embeddable_anomaly_charts_container.test.tsx.snap b/x-pack/plugins/ml/public/embeddables/anomaly_charts/__snapshots__/embeddable_anomaly_charts_container.test.tsx.snap index cb9a915a105a8..531db022eb4ad 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/__snapshots__/embeddable_anomaly_charts_container.test.tsx.snap +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/__snapshots__/embeddable_anomaly_charts_container.test.tsx.snap @@ -31,6 +31,7 @@ Object { "barTarget": undefined, "maxBars": undefined, }, + "timeRange": undefined, "timefilter": Object { "calculateBounds": [MockFunction], "createFilter": [MockFunction], diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx index 5a22cb7809a8c..95012ed2e890b 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx @@ -163,7 +163,7 @@ describe('EmbeddableAnomalyChartsContainer', () => { }); test('should render an error in case it could not fetch the ML charts data', async () => { - (useAnomalyChartsInputResolver as jest.Mock).mockReturnValueOnce({ + (useAnomalyChartsInputResolver as jest.Mock).mockReturnValue({ chartsData: undefined, isLoading: false, error: 'No anomalies', diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx index a0275176afa24..e9a22f1ad244e 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx @@ -11,6 +11,7 @@ import { Observable } from 'rxjs'; import { FormattedMessage } from '@kbn/i18n-react'; import { throttle } from 'lodash'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import useObservable from 'react-use/lib/useObservable'; import { useEmbeddableExecutionContext } from '../common/use_embeddable_execution_context'; import { useAnomalyChartsInputResolver } from './use_anomaly_charts_input_resolver'; import type { IAnomalyChartsEmbeddable } from './anomaly_charts_embeddable'; @@ -90,6 +91,8 @@ export const EmbeddableAnomalyChartsContainer: FC { onInputChange({ severityThreshold: severity.val, @@ -204,6 +207,7 @@ export const EmbeddableAnomalyChartsContainer: FC )} diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index e4a36b379ff49..6fa68a617eead 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -25,6 +25,7 @@ export { apmOperationsTab, apmLabsButton, enableInfrastructureHostsView, + enableServiceMetrics, enableAwsLambdaMetrics, } from './ui_settings_keys'; diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 8167c9a2c5ef5..f41e492d25050 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -21,3 +21,4 @@ export const apmOperationsTab = 'observability:apmOperationsTab'; export const apmLabsButton = 'observability:apmLabsButton'; export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView'; export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics'; +export const enableServiceMetrics = 'observability:apmEnableServiceMetrics'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 21296a9b3e35a..2d79e3b0e5e0a 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -23,6 +23,7 @@ import { apmOperationsTab, apmLabsButton, enableInfrastructureHostsView, + enableServiceMetrics, enableAwsLambdaMetrics, } from '../common/ui_settings_keys'; @@ -168,6 +169,21 @@ export const uiSettings: Record = { requiresPageReload: true, showInLabs: true, }, + [enableServiceMetrics]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.apmEnableServiceMetrics', { + defaultMessage: 'Service metrics', + }), + value: false, + description: i18n.translate('xpack.observability.apmEnableServiceMetricsGroupsDescription', { + defaultMessage: + '{technicalPreviewLabel} Enables Service metrics. When is enabled, additional configuration in APM Server is required.', + values: { technicalPreviewLabel: `[${technicalPreviewLabel}]` }, + }), + schema: schema.boolean(), + requiresPageReload: true, + showInLabs: true, + }, [apmServiceInventoryOptimizedSorting]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.apmServiceInventoryOptimizedSorting', { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index 3727675d99852..b6332bcd47aa4 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -10,6 +10,7 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { Filter, buildEsQuery, EsQueryConfig } from '@kbn/es-query'; import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { + AlertConsumers, getEsQueryConfig, getSafeSortIds, isValidFeatureId, @@ -674,7 +675,9 @@ export class AlertsClient { if (index == null) { throw new Error(`This feature id ${feature} should be associated to an alert index`); } - return index?.getPrimaryAlias(this.spaceId ?? '*') ?? ''; + return ( + index?.getPrimaryAlias(feature === AlertConsumers.SIEM ? this.spaceId ?? '*' : '*') ?? '' + ); }); return toReturn; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts index 3aab67794669f..dc339bf565cdc 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts @@ -33,6 +33,8 @@ export const LIST_TRUSTED_APPLICATION = 'trusted_application'; export const INSIGHTS_CHANNEL = 'security-insights-v1'; +export const TASK_METRICS_CHANNEL = 'task-metrics'; + export const DEFAULT_ADVANCED_POLICY_CONFIG_SETTINGS = { linux: { advanced: { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index a07995029cd76..330add4425192 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -24,6 +24,7 @@ import { metricsResponseToValueListMetaData, tlog, setIsElasticCloudDeployment, + createTaskMetric, } from './helpers'; import type { ESClusterInfo, ESLicense, ExceptionListItem } from './types'; import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types'; @@ -931,3 +932,42 @@ describe('test tlog', () => { expect(logger.debug).toHaveBeenCalled(); }); }); + +describe('test create task metrics', () => { + test('can succeed when all parameters are given', async () => { + const stubTaskName = 'test'; + const stubPassed = true; + const stubStartTime = Date.now(); + await new Promise((r) => setTimeout(r, 11)); + const response = createTaskMetric(stubTaskName, stubPassed, stubStartTime); + const { + time_executed_in_ms: timeExecutedInMs, + start_time: startTime, + end_time: endTime, + ...rest + } = response; + expect(timeExecutedInMs).toBeGreaterThan(10); + expect(rest).toEqual({ + name: 'test', + passed: true, + }); + }); + test('can succeed when error given', async () => { + const stubTaskName = 'test'; + const stubPassed = false; + const stubStartTime = Date.now(); + const errorMessage = 'failed'; + const response = createTaskMetric(stubTaskName, stubPassed, stubStartTime, errorMessage); + const { + time_executed_in_ms: timeExecutedInMs, + start_time: startTime, + end_time: endTime, + ...rest + } = response; + expect(rest).toEqual({ + name: 'test', + passed: false, + error_message: 'failed', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index 0c42a35a317e7..0fd7a0f6604c9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -22,6 +22,7 @@ import type { ValueListExceptionListResponseAggregation, ValueListItemsResponseAggregation, ValueListIndicatorMatchResponseAggregation, + TaskMetric, } from './types'; import { LIST_DETECTION_RULE_EXCEPTION, @@ -280,3 +281,20 @@ export const tlog = (logger: Logger, message: string) => { logger.debug(message); } }; + +export const createTaskMetric = ( + name: string, + passed: boolean, + startTime: number, + errorMessage?: string +): TaskMetric => { + const endTime = Date.now(); + return { + name, + passed, + time_executed_in_ms: endTime - startTime, + start_time: startTime, + end_time: endTime, + error_message: errorMessage, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts index 1f22a4c97327d..4562cbb725cb4 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts @@ -6,8 +6,12 @@ */ import type { Logger } from '@kbn/core/server'; -import { LIST_DETECTION_RULE_EXCEPTION, TELEMETRY_CHANNEL_LISTS } from '../constants'; -import { batchTelemetryRecords, templateExceptionList, tlog } from '../helpers'; +import { + LIST_DETECTION_RULE_EXCEPTION, + TELEMETRY_CHANNEL_LISTS, + TASK_METRICS_CHANNEL, +} from '../constants'; +import { batchTelemetryRecords, templateExceptionList, tlog, createTaskMetric } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { ExceptionListItem, ESClusterInfo, ESLicense, RuleSearchResult } from '../types'; @@ -27,74 +31,87 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { - tlog(logger, 'test'); - const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ - receiver.fetchClusterInfo(), - receiver.fetchLicenseInfo(), - ]); + const startTime = Date.now(); + const taskName = 'Security Solution Detection Rule Lists Telemetry'; + try { + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); - const clusterInfo = - clusterInfoPromise.status === 'fulfilled' - ? clusterInfoPromise.value - : ({} as ESClusterInfo); - const licenseInfo = - licenseInfoPromise.status === 'fulfilled' - ? licenseInfoPromise.value - : ({} as ESLicense | undefined); + const clusterInfo = + clusterInfoPromise.status === 'fulfilled' + ? clusterInfoPromise.value + : ({} as ESClusterInfo); + const licenseInfo = + licenseInfoPromise.status === 'fulfilled' + ? licenseInfoPromise.value + : ({} as ESLicense | undefined); - // Lists Telemetry: Detection Rules + // Lists Telemetry: Detection Rules - const { body: prebuiltRules } = await receiver.fetchDetectionRules(); + const { body: prebuiltRules } = await receiver.fetchDetectionRules(); - if (!prebuiltRules) { - tlog(logger, 'no prebuilt rules found'); - return 0; - } + if (!prebuiltRules) { + tlog(logger, 'no prebuilt rules found'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return 0; + } - const cacheArray = prebuiltRules.hits.hits.reduce((cache, searchHit) => { - const rule = searchHit._source as RuleSearchResult; - const ruleId = rule.alert.params.ruleId; + const cacheArray = prebuiltRules.hits.hits.reduce((cache, searchHit) => { + const rule = searchHit._source as RuleSearchResult; + const ruleId = rule.alert.params.ruleId; - const shouldNotProcess = - rule === null || - rule === undefined || - ruleId === null || - ruleId === undefined || - searchHit._source?.alert.params.exceptionsList.length === 0; + const shouldNotProcess = + rule === null || + rule === undefined || + ruleId === null || + ruleId === undefined || + searchHit._source?.alert.params.exceptionsList.length === 0; - if (shouldNotProcess) { - return cache; - } + if (shouldNotProcess) { + return cache; + } - cache.push(rule); - return cache; - }, [] as RuleSearchResult[]); + cache.push(rule); + return cache; + }, [] as RuleSearchResult[]); - const detectionRuleExceptions = [] as ExceptionListItem[]; - for (const item of cacheArray) { - const ruleVersion = item.alert.params.version; + const detectionRuleExceptions = [] as ExceptionListItem[]; + for (const item of cacheArray) { + const ruleVersion = item.alert.params.version; - for (const ex of item.alert.params.exceptionsList) { - const listItem = await receiver.fetchDetectionExceptionList(ex.list_id, ruleVersion); - for (const exceptionItem of listItem.data) { - detectionRuleExceptions.push(exceptionItem); + for (const ex of item.alert.params.exceptionsList) { + const listItem = await receiver.fetchDetectionExceptionList(ex.list_id, ruleVersion); + for (const exceptionItem of listItem.data) { + detectionRuleExceptions.push(exceptionItem); + } } } - } - const detectionRuleExceptionsJson = templateExceptionList( - detectionRuleExceptions, - clusterInfo, - licenseInfo, - LIST_DETECTION_RULE_EXCEPTION - ); - tlog(logger, `Detection rule exception json length ${detectionRuleExceptionsJson.length}`); - const batches = batchTelemetryRecords(detectionRuleExceptionsJson, maxTelemetryBatch); - for (const batch of batches) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + const detectionRuleExceptionsJson = templateExceptionList( + detectionRuleExceptions, + clusterInfo, + licenseInfo, + LIST_DETECTION_RULE_EXCEPTION + ); + tlog(logger, `Detection rule exception json length ${detectionRuleExceptionsJson.length}`); + const batches = batchTelemetryRecords(detectionRuleExceptionsJson, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + } + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return detectionRuleExceptions.length; + } catch (err) { + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); + return 0; } - - return detectionRuleExceptions.length; }, }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts index 45d3eeb40a801..a83326334c9d7 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts @@ -46,5 +46,6 @@ describe('diagnostics telemetry task test', () => { expect(mockTelemetryEventsSender.queueTelemetryEvents).toHaveBeenCalledWith( testDiagnosticsAlerts.hits.hits.flatMap((doc) => [doc._source]) ); + expect(mockTelemetryEventsSender.sendOnDemand).toBeCalledTimes(1); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts index 579e0e6cf9675..5c4604289eb51 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts @@ -6,11 +6,12 @@ */ import type { Logger } from '@kbn/core/server'; -import { tlog, getPreviousDiagTaskTimestamp } from '../helpers'; +import { tlog, getPreviousDiagTaskTimestamp, createTaskMetric } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { TelemetryEvent } from '../types'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; +import { TASK_METRICS_CHANNEL } from '../constants'; export function createTelemetryDiagnosticsTaskConfig() { return { @@ -27,26 +28,41 @@ export function createTelemetryDiagnosticsTaskConfig() { sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { - if (!taskExecutionPeriod.last) { - throw new Error('last execution timestamp is required'); - } + const startTime = Date.now(); + const taskName = 'Security Solution Telemetry Diagnostics task'; + try { + if (!taskExecutionPeriod.last) { + throw new Error('last execution timestamp is required'); + } - const response = await receiver.fetchDiagnosticAlerts( - taskExecutionPeriod.last, - taskExecutionPeriod.current - ); + const response = await receiver.fetchDiagnosticAlerts( + taskExecutionPeriod.last, + taskExecutionPeriod.current + ); - const hits = response.hits?.hits || []; - if (!Array.isArray(hits) || !hits.length) { - tlog(logger, 'no diagnostic alerts retrieved'); + const hits = response.hits?.hits || []; + if (!Array.isArray(hits) || !hits.length) { + tlog(logger, 'no diagnostic alerts retrieved'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return 0; + } + tlog(logger, `Received ${hits.length} diagnostic alerts`); + const diagAlerts: TelemetryEvent[] = hits.flatMap((h) => + h._source != null ? [h._source] : [] + ); + sender.queueTelemetryEvents(diagAlerts); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return diagAlerts.length; + } catch (err) { + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); return 0; } - tlog(logger, `Received ${hits.length} diagnostic alerts`); - const diagAlerts: TelemetryEvent[] = hits.flatMap((h) => - h._source != null ? [h._source] : [] - ); - sender.queueTelemetryEvents(diagAlerts); - return diagAlerts.length; }, }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index c3c1cdf54e4d9..d3c40b29e218f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -27,9 +27,10 @@ import { getPreviousDailyTaskTimestamp, isPackagePolicyList, tlog, + createTaskMetric, } from '../helpers'; import type { PolicyData } from '../../../../common/endpoint/types'; -import { TELEMETRY_CHANNEL_ENDPOINT_META } from '../constants'; +import { TELEMETRY_CHANNEL_ENDPOINT_META, TASK_METRICS_CHANNEL } from '../constants'; // Endpoint agent uses this Policy ID while it's installing. const DefaultEndpointPolicyIdToIgnore = '00000000-0000-0000-0000-000000000000'; @@ -58,294 +59,320 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { - tlog(logger, 'test'); - if (!taskExecutionPeriod.last) { - throw new Error('last execution timestamp is required'); - } - const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ - receiver.fetchClusterInfo(), - receiver.fetchLicenseInfo(), - ]); - - const clusterInfo = - clusterInfoPromise.status === 'fulfilled' - ? clusterInfoPromise.value - : ({} as ESClusterInfo); - const licenseInfo = - licenseInfoPromise.status === 'fulfilled' - ? licenseInfoPromise.value - : ({} as ESLicense | undefined); - - const endpointData = await fetchEndpointData( - receiver, - taskExecutionPeriod.last, - taskExecutionPeriod.current - ); - - /** STAGE 1 - Fetch Endpoint Agent Metrics - * - * Reads Endpoint Agent metrics out of the `.ds-metrics-endpoint.metrics` data stream - * and buckets them by Endpoint Agent id and sorts by the top hit. The EP agent will - * report its metrics once per day OR every time a policy change has occured. If - * a metric document(s) exists for an EP agent we map to fleet agent and policy - */ - if (endpointData.endpointMetrics === undefined) { - tlog(logger, `no endpoint metrics to report`); - return 0; - } + const startTime = Date.now(); + const taskName = 'Security Solution Telemetry Endpoint Metrics and Info task'; + try { + if (!taskExecutionPeriod.last) { + throw new Error('last execution timestamp is required'); + } + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); + + const clusterInfo = + clusterInfoPromise.status === 'fulfilled' + ? clusterInfoPromise.value + : ({} as ESClusterInfo); + const licenseInfo = + licenseInfoPromise.status === 'fulfilled' + ? licenseInfoPromise.value + : ({} as ESLicense | undefined); + + const endpointData = await fetchEndpointData( + receiver, + taskExecutionPeriod.last, + taskExecutionPeriod.current + ); - const { body: endpointMetricsResponse } = endpointData.endpointMetrics as unknown as { - body: EndpointMetricsAggregation; - }; + /** STAGE 1 - Fetch Endpoint Agent Metrics + * + * Reads Endpoint Agent metrics out of the `.ds-metrics-endpoint.metrics` data stream + * and buckets them by Endpoint Agent id and sorts by the top hit. The EP agent will + * report its metrics once per day OR every time a policy change has occured. If + * a metric document(s) exists for an EP agent we map to fleet agent and policy + */ + if (endpointData.endpointMetrics === undefined) { + tlog(logger, `no endpoint metrics to report`); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return 0; + } - if (endpointMetricsResponse.aggregations === undefined) { - tlog(logger, `no endpoint metrics to report`); - return 0; - } + const { body: endpointMetricsResponse } = endpointData.endpointMetrics as unknown as { + body: EndpointMetricsAggregation; + }; - const telemetryUsageCounter = sender.getTelemetryUsageCluster(); - telemetryUsageCounter?.incrementCounter({ - counterName: createUsageCounterLabel( - usageLabelPrefix.concat(['payloads', TELEMETRY_CHANNEL_ENDPOINT_META]) - ), - counterType: 'num_endpoint', - incrementBy: endpointMetricsResponse.aggregations.endpoint_count.value, - }); - - const endpointMetrics = endpointMetricsResponse.aggregations.endpoint_agents.buckets.map( - (epMetrics) => { - return { - endpoint_agent: epMetrics.latest_metrics.hits.hits[0]._source.agent.id, - endpoint_version: epMetrics.latest_metrics.hits.hits[0]._source.agent.version, - endpoint_metrics: epMetrics.latest_metrics.hits.hits[0]._source, - }; + if (endpointMetricsResponse.aggregations === undefined) { + tlog(logger, `no endpoint metrics to report`); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return 0; } - ); - - /** STAGE 2 - Fetch Fleet Agent Config - * - * As the policy id + policy version does not exist on the Endpoint Metrics document - * we need to fetch information about the Fleet Agent and sync the metrics document - * with the Agent's policy data. - * - */ - const agentsResponse = endpointData.fleetAgentsResponse; - - if (agentsResponse === undefined) { - tlog(logger, 'no fleet agent information available'); - return 0; - } - const fleetAgents = agentsResponse.agents.reduce((cache, agent) => { - if (agent.id === DefaultEndpointPolicyIdToIgnore) { + const telemetryUsageCounter = sender.getTelemetryUsageCluster(); + telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel( + usageLabelPrefix.concat(['payloads', TELEMETRY_CHANNEL_ENDPOINT_META]) + ), + counterType: 'num_endpoint', + incrementBy: endpointMetricsResponse.aggregations.endpoint_count.value, + }); + + const endpointMetrics = endpointMetricsResponse.aggregations.endpoint_agents.buckets.map( + (epMetrics) => { + return { + endpoint_agent: epMetrics.latest_metrics.hits.hits[0]._source.agent.id, + endpoint_version: epMetrics.latest_metrics.hits.hits[0]._source.agent.version, + endpoint_metrics: epMetrics.latest_metrics.hits.hits[0]._source, + }; + } + ); + + /** STAGE 2 - Fetch Fleet Agent Config + * + * As the policy id + policy version does not exist on the Endpoint Metrics document + * we need to fetch information about the Fleet Agent and sync the metrics document + * with the Agent's policy data. + * + */ + const agentsResponse = endpointData.fleetAgentsResponse; + + if (agentsResponse === undefined) { + tlog(logger, 'no fleet agent information available'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return 0; + } + + const fleetAgents = agentsResponse.agents.reduce((cache, agent) => { + if (agent.id === DefaultEndpointPolicyIdToIgnore) { + return cache; + } + + if (agent.policy_id !== null && agent.policy_id !== undefined) { + cache.set(agent.id, agent.policy_id); + } + return cache; + }, new Map()); + + const endpointPolicyCache = new Map(); + for (const policyInfo of fleetAgents.values()) { + if ( + policyInfo !== null && + policyInfo !== undefined && + !endpointPolicyCache.has(policyInfo) + ) { + tlog(logger, `policy info exists as ${policyInfo}`); + const agentPolicy = await receiver.fetchPolicyConfigs(policyInfo); + const packagePolicies = agentPolicy?.package_policies; + + if (packagePolicies !== undefined && isPackagePolicyList(packagePolicies)) { + tlog(logger, `package policy exists as ${JSON.stringify(packagePolicies)}`); + packagePolicies + .map((pPolicy) => pPolicy as PolicyData) + .forEach((pPolicy) => { + if ( + pPolicy.inputs[0]?.config !== undefined && + pPolicy.inputs[0]?.config !== null + ) { + pPolicy.inputs.forEach((input) => { + if ( + input.type === FLEET_ENDPOINT_PACKAGE && + input?.config !== undefined && + policyInfo !== undefined + ) { + endpointPolicyCache.set(policyInfo, pPolicy); + } + }); + } + }); + } + } } - if (agent.policy_id !== null && agent.policy_id !== undefined) { - cache.set(agent.id, agent.policy_id); + /** STAGE 3 - Fetch Endpoint Policy Responses + * + * Reads Endpoint Agent policy responses out of the `.ds-metrics-endpoint.policy*` data + * stream and creates a local K/V structure that stores the policy response (V) with + * the Endpoint Agent Id (K). A value will only exist if there has been a endpoint + * enrolled in the last 24 hours OR a policy change has occurred. We only send + * non-successful responses. If the field is null, we assume no responses in + * the last 24h or no failures/warnings in the policy applied. + * + */ + const { body: failedPolicyResponses } = endpointData.epPolicyResponse as unknown as { + body: EndpointPolicyResponseAggregation; + }; + + // If there is no policy responses in the 24h > now then we will continue + const policyResponses = failedPolicyResponses.aggregations + ? failedPolicyResponses.aggregations.policy_responses.buckets.reduce( + (cache, endpointAgentId) => { + const doc = endpointAgentId.latest_response.hits.hits[0]; + cache.set(endpointAgentId.key, doc); + return cache; + }, + new Map() + ) + : new Map(); + + tlog( + logger, + `policy responses exists as ${JSON.stringify(Object.fromEntries(policyResponses))}` + ); + + /** STAGE 4 - Fetch Endpoint Agent Metadata + * + * Reads Endpoint Agent metadata out of the `.ds-metrics-endpoint.metadata` data stream + * and buckets them by Endpoint Agent id and sorts by the top hit. The EP agent will + * report its metadata once per day OR every time a policy change has occured. If + * a metadata document(s) exists for an EP agent we map to fleet agent and policy + */ + if (endpointData.endpointMetadata === undefined) { + tlog(logger, `no endpoint metadata to report`); } - return cache; - }, new Map()); - - const endpointPolicyCache = new Map(); - for (const policyInfo of fleetAgents.values()) { - if ( - policyInfo !== null && - policyInfo !== undefined && - !endpointPolicyCache.has(policyInfo) - ) { - tlog(logger, `policy info exists as ${policyInfo}`); - const agentPolicy = await receiver.fetchPolicyConfigs(policyInfo); - const packagePolicies = agentPolicy?.package_policies; - - if (packagePolicies !== undefined && isPackagePolicyList(packagePolicies)) { - tlog(logger, `package policy exists as ${JSON.stringify(packagePolicies)}`); - packagePolicies - .map((pPolicy) => pPolicy as PolicyData) - .forEach((pPolicy) => { - if (pPolicy.inputs[0]?.config !== undefined && pPolicy.inputs[0]?.config !== null) { - pPolicy.inputs.forEach((input) => { - if ( - input.type === FLEET_ENDPOINT_PACKAGE && - input?.config !== undefined && - policyInfo !== undefined - ) { - endpointPolicyCache.set(policyInfo, pPolicy); - } - }); - } - }); - } + const { body: endpointMetadataResponse } = endpointData.endpointMetadata as unknown as { + body: EndpointMetadataAggregation; + }; + + if (endpointMetadataResponse.aggregations === undefined) { + tlog(logger, `no endpoint metadata to report`); } - } - /** STAGE 3 - Fetch Endpoint Policy Responses - * - * Reads Endpoint Agent policy responses out of the `.ds-metrics-endpoint.policy*` data - * stream and creates a local K/V structure that stores the policy response (V) with - * the Endpoint Agent Id (K). A value will only exist if there has been a endpoint - * enrolled in the last 24 hours OR a policy change has occurred. We only send - * non-successful responses. If the field is null, we assume no responses in - * the last 24h or no failures/warnings in the policy applied. - * - */ - const { body: failedPolicyResponses } = endpointData.epPolicyResponse as unknown as { - body: EndpointPolicyResponseAggregation; - }; - - // If there is no policy responses in the 24h > now then we will continue - const policyResponses = failedPolicyResponses.aggregations - ? failedPolicyResponses.aggregations.policy_responses.buckets.reduce( + const endpointMetadata = + endpointMetadataResponse.aggregations.endpoint_metadata.buckets.reduce( (cache, endpointAgentId) => { - const doc = endpointAgentId.latest_response.hits.hits[0]; + const doc = endpointAgentId.latest_metadata.hits.hits[0]; cache.set(endpointAgentId.key, doc); return cache; }, - new Map() - ) - : new Map(); - - tlog( - logger, - `policy responses exists as ${JSON.stringify(Object.fromEntries(policyResponses))}` - ); - - /** STAGE 4 - Fetch Endpoint Agent Metadata - * - * Reads Endpoint Agent metadata out of the `.ds-metrics-endpoint.metadata` data stream - * and buckets them by Endpoint Agent id and sorts by the top hit. The EP agent will - * report its metadata once per day OR every time a policy change has occured. If - * a metadata document(s) exists for an EP agent we map to fleet agent and policy - */ - if (endpointData.endpointMetadata === undefined) { - tlog(logger, `no endpoint metadata to report`); - } - - const { body: endpointMetadataResponse } = endpointData.endpointMetadata as unknown as { - body: EndpointMetadataAggregation; - }; - - if (endpointMetadataResponse.aggregations === undefined) { - tlog(logger, `no endpoint metadata to report`); - } - - const endpointMetadata = - endpointMetadataResponse.aggregations.endpoint_metadata.buckets.reduce( - (cache, endpointAgentId) => { - const doc = endpointAgentId.latest_metadata.hits.hits[0]; - cache.set(endpointAgentId.key, doc); - return cache; - }, - new Map() + new Map() + ); + tlog( + logger, + `endpoint metadata exists as ${JSON.stringify(Object.fromEntries(endpointMetadata))}` ); - tlog( - logger, - `endpoint metadata exists as ${JSON.stringify(Object.fromEntries(endpointMetadata))}` - ); - /** STAGE 5 - Create the telemetry log records - * - * Iterates through the endpoint metrics documents at STAGE 1 and joins them together - * to form the telemetry log that is sent back to Elastic Security developers to - * make improvements to the product. - * - */ - try { - const telemetryPayloads = endpointMetrics.map((endpoint) => { - let policyConfig = null; - let failedPolicy = null; - let endpointMetadataById = null; - - const fleetAgentId = endpoint.endpoint_metrics.elastic.agent.id; - const endpointAgentId = endpoint.endpoint_agent; - - const policyInformation = fleetAgents.get(fleetAgentId); - if (policyInformation) { - policyConfig = endpointPolicyCache.get(policyInformation) || null; - - if (policyConfig) { - failedPolicy = policyResponses.get(endpointAgentId); + /** STAGE 5 - Create the telemetry log records + * + * Iterates through the endpoint metrics documents at STAGE 1 and joins them together + * to form the telemetry log that is sent back to Elastic Security developers to + * make improvements to the product. + * + */ + try { + const telemetryPayloads = endpointMetrics.map((endpoint) => { + let policyConfig = null; + let failedPolicy = null; + let endpointMetadataById = null; + + const fleetAgentId = endpoint.endpoint_metrics.elastic.agent.id; + const endpointAgentId = endpoint.endpoint_agent; + + const policyInformation = fleetAgents.get(fleetAgentId); + if (policyInformation) { + policyConfig = endpointPolicyCache.get(policyInformation) || null; + + if (policyConfig) { + failedPolicy = policyResponses.get(endpointAgentId); + } } - } - if (endpointMetadata) { - endpointMetadataById = endpointMetadata.get(endpointAgentId); - } + if (endpointMetadata) { + endpointMetadataById = endpointMetadata.get(endpointAgentId); + } - const { - cpu, - memory, - uptime, - documents_volume: documentsVolume, - malicious_behavior_rules: maliciousBehaviorRules, - system_impact: systemImpact, - threads, - event_filter: eventFilter, - } = endpoint.endpoint_metrics.Endpoint.metrics; - const endpointPolicyDetail = extractEndpointPolicyConfig(policyConfig); - if (endpointPolicyDetail) { - endpointPolicyDetail.value = addDefaultAdvancedPolicyConfigSettings( - endpointPolicyDetail.value - ); - } - return { - '@timestamp': taskExecutionPeriod.current, - cluster_uuid: clusterInfo.cluster_uuid, - cluster_name: clusterInfo.cluster_name, - license_id: licenseInfo?.uid, - endpoint_id: endpointAgentId, - endpoint_version: endpoint.endpoint_version, - endpoint_package_version: policyConfig?.package?.version || null, - endpoint_metrics: { - cpu: cpu.endpoint, - memory: memory.endpoint.private, + const { + cpu, + memory, uptime, - documentsVolume, - maliciousBehaviorRules, - systemImpact, + documents_volume: documentsVolume, + malicious_behavior_rules: maliciousBehaviorRules, + system_impact: systemImpact, threads, - eventFilter, - }, - endpoint_meta: { - os: endpoint.endpoint_metrics.host.os, - capabilities: - endpointMetadataById !== null && endpointMetadataById !== undefined - ? endpointMetadataById._source.Endpoint.capabilities - : [], - }, - policy_config: endpointPolicyDetail !== null ? endpointPolicyDetail : {}, - policy_response: - failedPolicy !== null && failedPolicy !== undefined - ? { - agent_policy_status: failedPolicy._source.event.agent_id_status, - manifest_version: - failedPolicy._source.Endpoint.policy.applied.artifacts.global.version, - status: failedPolicy._source.Endpoint.policy.applied.status, - actions: failedPolicy._source.Endpoint.policy.applied.actions - .map((action) => (action.status !== 'success' ? action : null)) - .filter((action) => action !== null), - configuration: failedPolicy._source.Endpoint.configuration, - state: failedPolicy._source.Endpoint.state, - } - : {}, - telemetry_meta: { - metrics_timestamp: endpoint.endpoint_metrics['@timestamp'], - }, - }; - }); - - /** - * STAGE 6 - Send the documents - * - * Send the documents in a batches of maxTelemetryBatch - */ - const batches = batchTelemetryRecords(telemetryPayloads, maxTelemetryBatch); - for (const batch of batches) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_META, batch); + event_filter: eventFilter, + } = endpoint.endpoint_metrics.Endpoint.metrics; + const endpointPolicyDetail = extractEndpointPolicyConfig(policyConfig); + if (endpointPolicyDetail) { + endpointPolicyDetail.value = addDefaultAdvancedPolicyConfigSettings( + endpointPolicyDetail.value + ); + } + return { + '@timestamp': taskExecutionPeriod.current, + cluster_uuid: clusterInfo.cluster_uuid, + cluster_name: clusterInfo.cluster_name, + license_id: licenseInfo?.uid, + endpoint_id: endpointAgentId, + endpoint_version: endpoint.endpoint_version, + endpoint_package_version: policyConfig?.package?.version || null, + endpoint_metrics: { + cpu: cpu.endpoint, + memory: memory.endpoint.private, + uptime, + documentsVolume, + maliciousBehaviorRules, + systemImpact, + threads, + eventFilter, + }, + endpoint_meta: { + os: endpoint.endpoint_metrics.host.os, + capabilities: + endpointMetadataById !== null && endpointMetadataById !== undefined + ? endpointMetadataById._source.Endpoint.capabilities + : [], + }, + policy_config: endpointPolicyDetail !== null ? endpointPolicyDetail : {}, + policy_response: + failedPolicy !== null && failedPolicy !== undefined + ? { + agent_policy_status: failedPolicy._source.event.agent_id_status, + manifest_version: + failedPolicy._source.Endpoint.policy.applied.artifacts.global.version, + status: failedPolicy._source.Endpoint.policy.applied.status, + actions: failedPolicy._source.Endpoint.policy.applied.actions + .map((action) => (action.status !== 'success' ? action : null)) + .filter((action) => action !== null), + configuration: failedPolicy._source.Endpoint.configuration, + state: failedPolicy._source.Endpoint.state, + } + : {}, + telemetry_meta: { + metrics_timestamp: endpoint.endpoint_metrics['@timestamp'], + }, + }; + }); + + /** + * STAGE 6 - Send the documents + * + * Send the documents in a batches of maxTelemetryBatch + */ + const batches = batchTelemetryRecords(telemetryPayloads, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_META, batch); + } + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return telemetryPayloads.length; + } catch (err) { + logger.warn(`could not complete endpoint alert telemetry task due to ${err?.message}`); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); + return 0; } - return telemetryPayloads.length; } catch (err) { - logger.warn(`could not complete endpoint alert telemetry task due to ${err?.message}`); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts index 44a6b3cf644f4..33d33924fcf36 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts @@ -10,8 +10,8 @@ import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { ESClusterInfo, ESLicense, TelemetryEvent } from '../types'; import type { TaskExecutionPeriod } from '../task'; -import { TELEMETRY_CHANNEL_DETECTION_ALERTS } from '../constants'; -import { batchTelemetryRecords, tlog } from '../helpers'; +import { TELEMETRY_CHANNEL_DETECTION_ALERTS, TASK_METRICS_CHANNEL } from '../constants'; +import { batchTelemetryRecords, tlog, createTaskMetric } from '../helpers'; import { copyAllowlistedFields, prebuiltRuleAllowlistFields } from '../filterlists'; export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: number) { @@ -28,6 +28,8 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { + const startTime = Date.now(); + const taskName = 'Security Solution - Prebuilt Rule and Elastic ML Alerts Telemetry'; try { const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ receiver.fetchClusterInfo(), @@ -54,6 +56,9 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n if (telemetryEvents.length === 0) { tlog(logger, 'no prebuilt rule alerts retrieved'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); return 0; } @@ -76,10 +81,15 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_DETECTION_ALERTS, batch); } - + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); return enrichedAlerts.length; } catch (err) { logger.error('could not complete prebuilt alerts telemetry task'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index a6023d809c6b0..08baef614c1b8 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -15,9 +15,10 @@ import { LIST_ENDPOINT_EVENT_FILTER, LIST_TRUSTED_APPLICATION, TELEMETRY_CHANNEL_LISTS, + TASK_METRICS_CHANNEL, } from '../constants'; import type { ESClusterInfo, ESLicense } from '../types'; -import { batchTelemetryRecords, templateExceptionList, tlog } from '../helpers'; +import { batchTelemetryRecords, templateExceptionList, tlog, createTaskMetric } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; @@ -36,88 +37,100 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { - let count = 0; + const startTime = Date.now(); + const taskName = 'Security Solution Lists Telemetry'; + try { + let count = 0; - const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ - receiver.fetchClusterInfo(), - receiver.fetchLicenseInfo(), - ]); + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); - const clusterInfo = - clusterInfoPromise.status === 'fulfilled' - ? clusterInfoPromise.value - : ({} as ESClusterInfo); - const licenseInfo = - licenseInfoPromise.status === 'fulfilled' - ? licenseInfoPromise.value - : ({} as ESLicense | undefined); - const FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS = 24; + const clusterInfo = + clusterInfoPromise.status === 'fulfilled' + ? clusterInfoPromise.value + : ({} as ESClusterInfo); + const licenseInfo = + licenseInfoPromise.status === 'fulfilled' + ? licenseInfoPromise.value + : ({} as ESLicense | undefined); + const FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS = 24; - // Lists Telemetry: Trusted Applications - const trustedApps = await receiver.fetchTrustedApplications(); - if (trustedApps?.data) { - const trustedAppsJson = templateExceptionList( - trustedApps.data, - clusterInfo, - licenseInfo, - LIST_TRUSTED_APPLICATION - ); - tlog(logger, `Trusted Apps: ${trustedAppsJson}`); - count += trustedAppsJson.length; + // Lists Telemetry: Trusted Applications + const trustedApps = await receiver.fetchTrustedApplications(); + if (trustedApps?.data) { + const trustedAppsJson = templateExceptionList( + trustedApps.data, + clusterInfo, + licenseInfo, + LIST_TRUSTED_APPLICATION + ); + tlog(logger, `Trusted Apps: ${trustedAppsJson}`); + count += trustedAppsJson.length; - const batches = batchTelemetryRecords(trustedAppsJson, maxTelemetryBatch); - for (const batch of batches) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + const batches = batchTelemetryRecords(trustedAppsJson, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + } } - } - // Lists Telemetry: Endpoint Exceptions + // Lists Telemetry: Endpoint Exceptions - const epExceptions = await receiver.fetchEndpointList(ENDPOINT_LIST_ID); - if (epExceptions?.data) { - const epExceptionsJson = templateExceptionList( - epExceptions.data, - clusterInfo, - licenseInfo, - LIST_ENDPOINT_EXCEPTION - ); - tlog(logger, `EP Exceptions: ${epExceptionsJson}`); - count += epExceptionsJson.length; + const epExceptions = await receiver.fetchEndpointList(ENDPOINT_LIST_ID); + if (epExceptions?.data) { + const epExceptionsJson = templateExceptionList( + epExceptions.data, + clusterInfo, + licenseInfo, + LIST_ENDPOINT_EXCEPTION + ); + tlog(logger, `EP Exceptions: ${epExceptionsJson}`); + count += epExceptionsJson.length; - const batches = batchTelemetryRecords(epExceptionsJson, maxTelemetryBatch); - for (const batch of batches) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + const batches = batchTelemetryRecords(epExceptionsJson, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + } } - } - // Lists Telemetry: Endpoint Event Filters + // Lists Telemetry: Endpoint Event Filters - const epFilters = await receiver.fetchEndpointList(ENDPOINT_EVENT_FILTERS_LIST_ID); - if (epFilters?.data) { - const epFiltersJson = templateExceptionList( - epFilters.data, - clusterInfo, - licenseInfo, - LIST_ENDPOINT_EVENT_FILTER - ); - tlog(logger, `EP Event Filters: ${epFiltersJson}`); - count += epFiltersJson.length; + const epFilters = await receiver.fetchEndpointList(ENDPOINT_EVENT_FILTERS_LIST_ID); + if (epFilters?.data) { + const epFiltersJson = templateExceptionList( + epFilters.data, + clusterInfo, + licenseInfo, + LIST_ENDPOINT_EVENT_FILTER + ); + tlog(logger, `EP Event Filters: ${epFiltersJson}`); + count += epFiltersJson.length; - const batches = batchTelemetryRecords(epFiltersJson, maxTelemetryBatch); - for (const batch of batches) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + const batches = batchTelemetryRecords(epFiltersJson, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + } } - } - // Value list meta data - const valueListMetaData = await receiver.fetchValueListMetaData( - FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS - ); - tlog(logger, `Value List Meta Data: ${JSON.stringify(valueListMetaData)}`); - if (valueListMetaData?.total_list_count) { - await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); + // Value list meta data + const valueListMetaData = await receiver.fetchValueListMetaData( + FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS + ); + tlog(logger, `Value List Meta Data: ${JSON.stringify(valueListMetaData)}`); + if (valueListMetaData?.total_list_count) { + await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); + } + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return count; + } catch (err) { + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); + return 0; } - return count; }, }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts index 7a460caa197d7..16794dfa3f68f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts @@ -60,6 +60,5 @@ describe('timeline telemetry task test', () => { expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled(); expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled(); expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled(); - expect(mockTelemetryEventsSender.sendOnDemand).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts index 8403bbd7f30fd..4fdfc4a726a35 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -17,9 +17,9 @@ import type { TimelineTelemetryTemplate, TimelineTelemetryEvent, } from '../types'; -import { TELEMETRY_CHANNEL_TIMELINE } from '../constants'; +import { TELEMETRY_CHANNEL_TIMELINE, TASK_METRICS_CHANNEL } from '../constants'; import { resolverEntity } from '../../../endpoint/routes/resolver/entity/utils/build_resolver_entity'; -import { tlog } from '../helpers'; +import { tlog, createTaskMetric } from '../helpers'; export function createTelemetryTimelineTaskConfig() { return { @@ -35,145 +35,159 @@ export function createTelemetryTimelineTaskConfig() { sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { - let counter = 0; - - tlog(logger, `Running task: ${taskId}`); - - const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ - receiver.fetchClusterInfo(), - receiver.fetchLicenseInfo(), - ]); - - const clusterInfo = - clusterInfoPromise.status === 'fulfilled' - ? clusterInfoPromise.value - : ({} as ESClusterInfo); - - const licenseInfo = - licenseInfoPromise.status === 'fulfilled' - ? licenseInfoPromise.value - : ({} as ESLicense | undefined); - - const now = moment(); - const startOfDay = now.startOf('day').toISOString(); - const endOfDay = now.endOf('day').toISOString(); - - const baseDocument = { - version: clusterInfo.version?.number, - cluster_name: clusterInfo.cluster_name, - cluster_uuid: clusterInfo.cluster_uuid, - license_uuid: licenseInfo?.uid, - }; - - // Fetch EP Alerts - - const endpointAlerts = await receiver.fetchTimelineEndpointAlerts(3); - - const aggregations = endpointAlerts?.aggregations as unknown as { - endpoint_alert_count: { value: number }; - }; - tlog(logger, `Endpoint alert count: ${aggregations?.endpoint_alert_count}`); - sender.getTelemetryUsageCluster()?.incrementCounter({ - counterName: 'telemetry_endpoint_alert', - counterType: 'endpoint_alert_count', - incrementBy: aggregations?.endpoint_alert_count.value, - }); - - // No EP Alerts -> Nothing to do - if ( - endpointAlerts.hits.hits?.length === 0 || - endpointAlerts.hits.hits?.length === undefined - ) { - tlog(logger, 'no endpoint alerts received. exiting telemetry task.'); - return counter; - } + const startTime = Date.now(); + const taskName = 'Security Solution Timeline telemetry'; + try { + let counter = 0; + + tlog(logger, `Running task: ${taskId}`); + + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); + + const clusterInfo = + clusterInfoPromise.status === 'fulfilled' + ? clusterInfoPromise.value + : ({} as ESClusterInfo); + + const licenseInfo = + licenseInfoPromise.status === 'fulfilled' + ? licenseInfoPromise.value + : ({} as ESLicense | undefined); + + const now = moment(); + const startOfDay = now.startOf('day').toISOString(); + const endOfDay = now.endOf('day').toISOString(); + + const baseDocument = { + version: clusterInfo.version?.number, + cluster_name: clusterInfo.cluster_name, + cluster_uuid: clusterInfo.cluster_uuid, + license_uuid: licenseInfo?.uid, + }; + + // Fetch EP Alerts + + const endpointAlerts = await receiver.fetchTimelineEndpointAlerts(3); + + const aggregations = endpointAlerts?.aggregations as unknown as { + endpoint_alert_count: { value: number }; + }; + tlog(logger, `Endpoint alert count: ${aggregations?.endpoint_alert_count}`); + sender.getTelemetryUsageCluster()?.incrementCounter({ + counterName: 'telemetry_endpoint_alert', + counterType: 'endpoint_alert_count', + incrementBy: aggregations?.endpoint_alert_count.value, + }); + + // No EP Alerts -> Nothing to do + if ( + endpointAlerts.hits.hits?.length === 0 || + endpointAlerts.hits.hits?.length === undefined + ) { + tlog(logger, 'no endpoint alerts received. exiting telemetry task.'); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return counter; + } - // Build process tree for each EP Alert recieved + // Build process tree for each EP Alert recieved - for (const alert of endpointAlerts.hits.hits) { - const eventId = alert._source ? alert._source['event.id'] : 'unknown'; - const alertUUID = alert._source ? alert._source['kibana.alert.uuid'] : 'unknown'; + for (const alert of endpointAlerts.hits.hits) { + const eventId = alert._source ? alert._source['event.id'] : 'unknown'; + const alertUUID = alert._source ? alert._source['kibana.alert.uuid'] : 'unknown'; - const entities = resolverEntity([alert]); + const entities = resolverEntity([alert]); - // Build Tree + // Build Tree - const tree = await receiver.buildProcessTree( - entities[0].id, - entities[0].schema, - startOfDay, - endOfDay - ); + const tree = await receiver.buildProcessTree( + entities[0].id, + entities[0].schema, + startOfDay, + endOfDay + ); - const nodeIds = [] as string[]; - if (Array.isArray(tree)) { - for (const node of tree) { - const nodeId = node?.id.toString(); - nodeIds.push(nodeId); + const nodeIds = [] as string[]; + if (Array.isArray(tree)) { + for (const node of tree) { + const nodeId = node?.id.toString(); + nodeIds.push(nodeId); + } } - } - sender.getTelemetryUsageCluster()?.incrementCounter({ - counterName: 'telemetry_timeline', - counterType: 'timeline_node_count', - incrementBy: nodeIds.length, - }); + sender.getTelemetryUsageCluster()?.incrementCounter({ + counterName: 'telemetry_timeline', + counterType: 'timeline_node_count', + incrementBy: nodeIds.length, + }); - // Fetch event lineage + // Fetch event lineage - const timelineEvents = await receiver.fetchTimelineEvents(nodeIds); - tlog(logger, `Timeline Events: ${JSON.stringify(timelineEvents)}`); - const eventsStore = new Map(); - for (const event of timelineEvents.hits.hits) { - const doc = event._source; + const timelineEvents = await receiver.fetchTimelineEvents(nodeIds); + tlog(logger, `Timeline Events: ${JSON.stringify(timelineEvents)}`); + const eventsStore = new Map(); + for (const event of timelineEvents.hits.hits) { + const doc = event._source; - if (doc !== null && doc !== undefined) { - const entityId = doc?.process?.entity_id?.toString(); - if (entityId !== null && entityId !== undefined) eventsStore.set(entityId, doc); + if (doc !== null && doc !== undefined) { + const entityId = doc?.process?.entity_id?.toString(); + if (entityId !== null && entityId !== undefined) eventsStore.set(entityId, doc); + } } - } - sender.getTelemetryUsageCluster()?.incrementCounter({ - counterName: 'telemetry_timeline', - counterType: 'timeline_event_count', - incrementBy: eventsStore.size, - }); + sender.getTelemetryUsageCluster()?.incrementCounter({ + counterName: 'telemetry_timeline', + counterType: 'timeline_event_count', + incrementBy: eventsStore.size, + }); - // Create telemetry record + // Create telemetry record - const telemetryTimeline: TimelineTelemetryEvent[] = []; - if (Array.isArray(tree)) { - for (const node of tree) { - const id = node.id.toString(); - const event = eventsStore.get(id); + const telemetryTimeline: TimelineTelemetryEvent[] = []; + if (Array.isArray(tree)) { + for (const node of tree) { + const id = node.id.toString(); + const event = eventsStore.get(id); - const timelineTelemetryEvent: TimelineTelemetryEvent = { - ...node, - event, - }; + const timelineTelemetryEvent: TimelineTelemetryEvent = { + ...node, + event, + }; - telemetryTimeline.push(timelineTelemetryEvent); + telemetryTimeline.push(timelineTelemetryEvent); + } } - } - if (telemetryTimeline.length >= 1) { - const record: TimelineTelemetryTemplate = { - '@timestamp': moment().toISOString(), - ...baseDocument, - alert_id: alertUUID, - event_id: eventId, - timeline: telemetryTimeline, - }; - - sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); - counter += 1; - } else { - tlog(logger, 'no events in timeline'); + if (telemetryTimeline.length >= 1) { + const record: TimelineTelemetryTemplate = { + '@timestamp': moment().toISOString(), + ...baseDocument, + alert_id: alertUUID, + event_id: eventId, + timeline: telemetryTimeline, + }; + + sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); + counter += 1; + } else { + tlog(logger, 'no events in timeline'); + } } + tlog(logger, `sent ${counter} timelines. concluding timeline task.`); + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, true, startTime), + ]); + return counter; + } catch (err) { + await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ + createTaskMetric(taskName, false, startTime, err.message), + ]); + return 0; } - - tlog(logger, `sent ${counter} timelines. concluding timeline task.`); - return counter; }, }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index 82b4fde4b5992..3e0d9c1b4e1d2 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -412,3 +412,12 @@ export interface ValueListIndicatorMatchResponseAggregation { vl_used_in_indicator_match_rule_count: { value: number }; }; } + +export interface TaskMetric { + name: string; + passed: boolean; + time_executed_in_ms: number; + start_time: number; + end_time: number; + error_message?: string; +} diff --git a/x-pack/plugins/stack_alerts/common/constants.ts b/x-pack/plugins/stack_alerts/common/constants.ts new file mode 100644 index 0000000000000..cac00873face2 --- /dev/null +++ b/x-pack/plugins/stack_alerts/common/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; diff --git a/x-pack/plugins/stack_alerts/common/index.ts b/x-pack/plugins/stack_alerts/common/index.ts index 65d05a298224d..1885cbb623b1d 100644 --- a/x-pack/plugins/stack_alerts/common/index.ts +++ b/x-pack/plugins/stack_alerts/common/index.ts @@ -9,4 +9,4 @@ /* eslint-disable @kbn/eslint/no_export_all */ export * from './config'; -export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; +export { STACK_ALERTS_FEATURE_ID } from './constants'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx index d0739baa1ec7a..92db8171450f3 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx @@ -10,14 +10,22 @@ import { EuiCallOut, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { fromKueryExpression, luceneStringToDsl } from '@kbn/es-query'; -import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; -import { DataView } from '@kbn/data-plugin/common'; +import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import type { DataView } from '@kbn/data-plugin/common'; import type { Query } from '@kbn/es-query'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import { GeoContainmentAlertParams } from '../types'; -import { EntityIndexExpression } from './expressions/entity_index_expression'; -import { EntityByExpression } from './expressions/entity_by_expression'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import type { CoreStart } from '@kbn/core/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants'; import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; +import { EntityByExpression } from './expressions/entity_by_expression'; +import { EntityIndexExpression } from './expressions/entity_index_expression'; +import type { GeoContainmentAlertParams } from '../types'; const DEFAULT_VALUES = { TRACKING_EVENT: '', @@ -34,6 +42,15 @@ const DEFAULT_VALUES = { DELAY_OFFSET_WITH_UNITS: '0m', }; +interface KibanaDeps { + http: HttpSetup; + docLinks: DocLinksStart; + uiSettings: IUiSettingsClient; + notifications: CoreStart['notifications']; + storage: IStorageWrapper; + usageCollection: UsageCollectionStart; +} + function validateQuery(query: Query) { try { // eslint-disable-next-line @typescript-eslint/no-unused-expressions @@ -62,6 +79,9 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< boundaryNameField, } = ruleParams; + const { http, docLinks, uiSettings, notifications, storage, usageCollection } = + useKibana().services; + const [indexPattern, _setIndexPattern] = useState({ id: '', title: '', @@ -198,6 +218,17 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< setIndexQueryInput(query); } }} + appName={STACK_ALERTS_FEATURE_ID} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> @@ -242,6 +273,17 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< setBoundaryIndexQueryInput(query); } }} + appName={STACK_ALERTS_FEATURE_ID} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> diff --git a/x-pack/plugins/synthetics/common/constants/rest_api.ts b/x-pack/plugins/synthetics/common/constants/rest_api.ts index 85f345c0972fb..d0d783e424f3a 100644 --- a/x-pack/plugins/synthetics/common/constants/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/rest_api.ts @@ -18,7 +18,7 @@ export enum API_URLS { PING_HISTOGRAM = `/internal/uptime/ping/histogram`, SNAPSHOT_COUNT = `/internal/uptime/snapshot/count`, SYNTHETICS_SUCCESSFUL_CHECK = `/internal/uptime/synthetics/check/success`, - JOURNEY_CREATE = `/internal/uptime/journey/{checkGroup}`, + JOURNEY = `/internal/uptime/journey/{checkGroup}`, JOURNEY_FAILED_STEPS = `/internal/uptime/journeys/failed_steps`, JOURNEY_SCREENSHOT = `/internal/uptime/journey/screenshot/{checkGroup}/{stepIndex}`, JOURNEY_SCREENSHOT_BLOCKS = `/internal/uptime/journey/screenshot/block`, diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index ed8ff0bc50e0d..7b9a1fe380a69 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -6,6 +6,6 @@ */ export enum SYNTHETICS_API_URLS { - MONITOR_STATUS = `/internal/synthetics/monitor/status`, SYNTHETICS_OVERVIEW = '/internal/synthetics/overview', + PINGS = '/internal/synthetics/pings', } diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts index c95f9c281dc92..6f2264d66f9b1 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts @@ -198,10 +198,23 @@ export const ScreenshotBlockDocType = t.type({ export type ScreenshotBlockDoc = t.TypeOf; +export interface PendingBlock { + status: 'pending' | 'loading'; +} + +export type StoreScreenshotBlock = ScreenshotBlockDoc | PendingBlock; +export interface ScreenshotBlockCache { + [hash: string]: StoreScreenshotBlock; +} + export function isScreenshotBlockDoc(data: unknown): data is ScreenshotBlockDoc { return isRight(ScreenshotBlockDocType.decode(data)); } +export function isPendingBlock(data: unknown): data is PendingBlock { + return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); +} + /** * Contains the fields requried by the Synthetics UI when utilizing screenshot refs. */ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx new file mode 100644 index 0000000000000..81ecb7dc60027 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.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 React, { CSSProperties } from 'react'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonIcon, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; + +import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context'; +import { JourneyStep } from '../../../../../../common/runtime_types'; + +import { StatusBadge, parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; +import { JourneyStepScreenshotWithLabel } from './journey_step_screenshot_with_label'; +import { StepDurationText } from './step_duration_text'; + +interface Props { + steps: JourneyStep[]; + error?: Error; + loading: boolean; + showStepNumber: boolean; +} + +export function isStepEnd(step: JourneyStep) { + return step.synthetics?.type === 'step/end'; +} + +export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false }: Props) => { + const { euiTheme } = useEuiTheme(); + const stepEnds: JourneyStep[] = steps.filter(isStepEnd); + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + const { basePath } = useSyntheticsSettingsContext(); + + const columns: Array> = [ + ...(showStepNumber + ? [ + { + field: 'synthetics.step.index', + name: '#', + render: (stepIndex: number, item: JourneyStep) => ( + + ), + }, + ] + : []), + { + align: 'left', + field: 'timestamp', + name: STEP_LABEL, + render: (_timestamp: string, item) => ( + + ), + mobileOptions: { + render: (item: JourneyStep) => ( + + + {item.synthetics?.step?.index!}. {item.synthetics?.step?.name} + + + ), + header: STEP_LABEL, + enlarge: true, + }, + }, + { + field: 'synthetics.step.status', + name: RESULT_LABEL, + render: (pingStatus: string) => , + }, + { + align: 'left', + name: STEP_DURATION, + render: (item: JourneyStep) => { + return ; + }, + mobileOptions: { + header: STEP_DURATION, + show: true, + }, + }, + { + align: 'right', + field: 'timestamp', + name: '', + mobileOptions: { show: false }, + render: (_val: string, item) => ( + + ), + }, + ]; + + return ( + <> + + + ); +}; + +const StepNumber = ({ + stepIndex, + step, + euiTheme, +}: { + stepIndex: number; + step: JourneyStep; + euiTheme: EuiThemeComputed; +}) => { + const status = parseBadgeStatus(step.synthetics?.step?.status ?? ''); + + return ( + + {stepIndex} + + ); +}; + +const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', { + defaultMessage: 'Result', +}); + +const STEP_LABEL = i18n.translate('xpack.synthetics.monitor.step.label', { + defaultMessage: 'Step', +}); + +const STEP_DURATION = i18n.translate('xpack.synthetics.monitor.step.duration.label', { + defaultMessage: 'Duration', +}); + +const VIEW_DETAILS = i18n.translate('xpack.synthetics.monitor.step.viewDetails', { + defaultMessage: 'View Details', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.tsx new file mode 100644 index 0000000000000..d877820ca24fd --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '../../../utils/testing/rtl_helpers'; +import { + EmptyThumbnail, + SCREENSHOT_LOADING_ARIA_LABEL, + SCREENSHOT_NOT_AVAILABLE, +} from './empty_thumbnail'; + +describe('EmptyThumbnail', () => { + it('renders a loading placeholder for loading state', () => { + const { getByLabelText, getByTestId } = render(); + + expect(getByTestId('stepScreenshotPlaceholderLoading')).toBeInTheDocument(); + expect(getByLabelText(SCREENSHOT_LOADING_ARIA_LABEL)); + }); + + it('renders no image available when not loading', () => { + const { queryByTestId, getByLabelText } = render(); + + expect(queryByTestId('stepScreenshotPlaceholderLoading')).not.toBeInTheDocument(); + expect(getByLabelText(SCREENSHOT_NOT_AVAILABLE)); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx new file mode 100644 index 0000000000000..bfc5851ade619 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { useEuiTheme, useEuiBackgroundColor, EuiIcon, EuiLoadingContent } from '@elastic/eui'; + +export const THUMBNAIL_WIDTH = 96; +export const THUMBNAIL_HEIGHT = 64; + +export const thumbnailStyle = css` + padding: 0; + margin: 0; + width: ${THUMBNAIL_WIDTH}px; + height: ${THUMBNAIL_HEIGHT}px; + object-fit: contain; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +`; + +export const EmptyThumbnail = ({ + isLoading = false, + width = THUMBNAIL_WIDTH, + height = THUMBNAIL_HEIGHT, +}: { + isLoading: boolean; + width?: number; + height?: number; +}) => { + const { euiTheme } = useEuiTheme(); + + return ( +
+ {isLoading ? ( + + ) : ( + + )} +
+ ); +}; + +export const SCREENSHOT_LOADING_ARIA_LABEL = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.ariaLabel', + { + defaultMessage: 'Step screenshot is being loaded.', + } +); + +export const SCREENSHOT_NOT_AVAILABLE = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.notAvailable', + { + defaultMessage: 'Step screenshot is not available.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx new file mode 100644 index 0000000000000..c738f07218842 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { screen } from '@elastic/eui/lib/test/rtl'; +import { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { JourneyStepImagePopover, StepImagePopoverProps } from './journey_step_image_popover'; +import { render } from '../../../utils/testing'; + +describe('JourneyStepImagePopover', () => { + let defaultProps: StepImagePopoverProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption', + imageCaption:
test caption element
, + imgSrc: 'http://sample.com/sampleImageSrc.png', + isImagePopoverOpen: false, + isStepFailed: false, + isLoading: false, + }; + }); + + it('opens displays full-size image on click, hides after close is clicked', async () => { + const { getByAltText } = render(); + + expect(screen.queryByTestSubject('deactivateFullScreenButton')).toBeNull(); + + const caption = getByAltText('test caption'); + fireEvent.click(caption); + + await waitFor(() => { + fireEvent.click(screen.getByTestSubject('deactivateFullScreenButton')); + }); + + await waitFor(() => { + expect(screen.queryByTestSubject('deactivateFullScreenButton')).toBeNull(); + }); + }); + + it('shows the popover when `isOpen` is true', () => { + defaultProps.isImagePopoverOpen = true; + + const { getByAltText } = render(); + + expect(getByAltText(`A larger version of the screenshot for this journey step's thumbnail.`)); + }); + + it('renders caption content', () => { + const { getByRole } = render(); + const image = getByRole('img'); + expect(image).toHaveAttribute('alt', 'test caption'); + expect(image).toHaveAttribute('src', 'http://sample.com/sampleImageSrc.png'); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx new file mode 100644 index 0000000000000..48ff7237223fb --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiImage, EuiPopover, useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; +import { useCompositeImage } from '../../../hooks/use_composite_image'; + +import { EmptyThumbnail, thumbnailStyle } from './empty_thumbnail'; + +const POPOVER_IMG_HEIGHT = 360; +const POPOVER_IMG_WIDTH = 640; + +interface ScreenshotImageProps { + captionContent: string; + imageCaption: JSX.Element; + isStepFailed: boolean; + isLoading: boolean; +} + +const ScreenshotThumbnail: React.FC = ({ + captionContent, + imageCaption, + imageData, + isStepFailed, + isLoading, +}) => { + return imageData ? ( + + ) : ( + + ); +}; +/** + * This component provides an intermediate step for composite images. It causes a loading spinner to appear + * while the image is being re-assembled, then calls the default image component and provides a data URL for the image. + */ +const RecomposedScreenshotImage: React.FC< + ScreenshotImageProps & { + imgRef: ScreenshotRefImageData; + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ + captionContent, + imageCaption, + imageData, + imgRef, + setImageData, + isStepFailed, + isLoading, +}) => { + // initially an undefined URL value is passed to the image display, and a loading spinner is rendered. + // `useCompositeImage` will call `setImageData` when the image is composited, and the updated `imageData` will display. + useCompositeImage(imgRef, setImageData, imageData); + + return ( + + ); +}; + +export interface StepImagePopoverProps { + captionContent: string; + imageCaption: JSX.Element; + imgSrc?: string; + imgRef?: ScreenshotRefImageData; + isImagePopoverOpen: boolean; + isStepFailed: boolean; + isLoading: boolean; +} + +const JourneyStepImage: React.FC< + Omit & { + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ + captionContent, + imageCaption, + imageData, + imgRef, + imgSrc, + setImageData, + isStepFailed, + isLoading, +}) => { + if (imgSrc) { + return ( + + ); + } else if (imgRef) { + return ( + + ); + } + return null; +}; + +export const JourneyStepImagePopover: React.FC = ({ + captionContent, + imageCaption, + imgRef, + imgSrc, + isImagePopoverOpen, + isStepFailed, + isLoading, +}) => { + const { euiTheme } = useEuiTheme(); + + const [imageData, setImageData] = React.useState(imgSrc || undefined); + + React.useEffect(() => { + // for legacy screenshots, when a new image arrives, we must overwrite it + if (imgSrc && imgSrc !== imageData) { + setImageData(imgSrc); + } + }, [imgSrc, imageData]); + + const setImageDataCallback = React.useCallback( + (newImageData: string | undefined) => setImageData(newImageData), + [setImageData] + ); + + const isImageLoading = isLoading || (!!imgRef && !imageData); + + return ( + + } + isOpen={isImagePopoverOpen} + closePopover={() => {}} + > + {imageData && !isLoading ? ( + + ) : ( + + )} + + ); +}; + +export const fullSizeImageAlt = i18n.translate('xpack.synthetics.monitor.step.thumbnail.alt', { + defaultMessage: `A larger version of the screenshot for this journey step's thumbnail.`, +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx new file mode 100644 index 0000000000000..4c95fade23d1a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, waitFor } from '@testing-library/react'; +import moment from 'moment'; +import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; +import { render } from '../../../utils/testing'; +import * as observabilityPublic from '@kbn/observability-plugin/public'; +import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; +import '../../../utils/testing/__mocks__/use_composite_image.mock'; +import { mockRef } from '../../../utils/testing/__mocks__/screenshot_ref.mock'; +import * as retrieveHooks from './use_retrieve_step_image'; + +jest.mock('@kbn/observability-plugin/public'); + +describe('JourneyStepScreenshotContainer', () => { + let checkGroup: string; + let timestamp: string; + const { FETCH_STATUS } = observabilityPublic; + + beforeAll(() => { + checkGroup = 'f58a484f-2ffb-11eb-9b35-025000000001'; + timestamp = '2020-11-26T15:28:56.896Z'; + }); + + it.each([[FETCH_STATUS.PENDING], [FETCH_STATUS.LOADING]])( + 'displays spinner when loading step image', + (fetchStatus) => { + jest + .spyOn(observabilityPublic, 'useFetcher') + .mockReturnValue({ status: fetchStatus, data: null, refetch: () => null, loading: true }); + const { getByTestId } = render( + + ); + expect(getByTestId('stepScreenshotPlaceholderLoading')).toBeInTheDocument(); + } + ); + + it('displays no image available when img src is unavailable and fetch status is successful', () => { + jest + .spyOn(observabilityPublic, 'useFetcher') + .mockReturnValue({ status: FETCH_STATUS.SUCCESS, data: null, refetch: () => null }); + const { getByTestId } = render( + + ); + expect(getByTestId('stepScreenshotNotAvailable')).toBeInTheDocument(); + }); + + it('displays image when img src is available from useFetcher', () => { + const src = 'http://sample.com/sampleImageSrc.png'; + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: { maxSteps: 2, stepName: 'test', src }, + attempts: 1, + }); + + const { container } = render( + + ); + expect(container.querySelector('img')?.src).toBe(src); + }); + + it('displays popover image when mouse enters img caption, and hides onLeave', async () => { + const src = 'http://sample.com/sampleImageSrc.png'; + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: { maxSteps: 1, stepName: null, src }, + attempts: 1, + }); + const { getByAltText, getAllByText, queryByAltText } = render( + + ); + + const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption[0]); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption[0]); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + }); + + it('handles screenshot ref data', async () => { + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: mockRef, + attempts: 1, + }); + + const { getByAltText, getByText, getByRole, getAllByText, queryByAltText } = render( + + ); + + await waitFor(() => getByRole('img')); + const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption[0]); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption[0]); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + expect(getByText('Step: 1 of 1')); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx new file mode 100644 index 0000000000000..6c2653f7efaa6 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import useIntersection from 'react-use/lib/useIntersection'; +import { i18n } from '@kbn/i18n'; + +import { + isScreenshotImageBlob, + isScreenshotRef, + ScreenshotRefImageData, +} from '../../../../../../common/runtime_types'; + +import { SyntheticsSettingsContext } from '../../../contexts'; + +import { useRetrieveStepImage } from './use_retrieve_step_image'; +import { ScreenshotOverlayFooter } from './screenshot_overlay_footer'; +import { JourneyStepImagePopover } from './journey_step_image_popover'; +import { EmptyThumbnail } from './empty_thumbnail'; + +interface Props { + checkGroup?: string; + stepLabels?: string[]; + stepStatus?: string; + initialStepNo?: number; + allStepsLoaded?: boolean; + retryFetchOnRevisit?: boolean; // Set to `true` fro "Run Once" / "Test Now" modes +} + +export const JourneyStepScreenshotContainer = ({ + stepLabels = [], + checkGroup, + stepStatus, + allStepsLoaded, + initialStepNo = 1, + retryFetchOnRevisit = false, +}: Props) => { + const [stepNumber, setStepNumber] = useState(initialStepNo); + const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false); + + const [stepImages, setStepImages] = useState([]); + + const intersectionRef = React.useRef(null); + + const { basePath } = useContext(SyntheticsSettingsContext); + + const imgPath = `${basePath}/internal/uptime/journey/screenshot/${checkGroup}/${stepNumber}`; + const stepLabel = stepLabels[stepNumber - 1] ?? ''; + + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1, + }); + + const [screenshotRef, setScreenshotRef] = useState(undefined); + + const isScreenshotRefValid = Boolean( + screenshotRef && screenshotRef?.ref?.screenshotRef?.synthetics?.step?.index === stepNumber + ); + const { data, loading } = useRetrieveStepImage({ + hasImage: Boolean(stepImages[stepNumber - 1]) || isScreenshotRefValid, + hasIntersected: Boolean(intersection && intersection.intersectionRatio === 1), + stepStatus, + imgPath, + retryFetchOnRevisit, + }); + + useEffect(() => { + if (isScreenshotRef(data)) { + setScreenshotRef(data); + } else if (isScreenshotImageBlob(data)) { + setStepImages((prevState) => [...prevState, data?.src]); + } + }, [data]); + + let imgSrc; + if (isScreenshotImageBlob(data)) { + imgSrc = stepImages?.[stepNumber - 1] ?? data.src; + } + + const captionContent = formatCaptionContent(stepNumber, data?.maxSteps); + + const [numberOfCaptions, setNumberOfCaptions] = useState(0); + + // Overlay Footer has next and previous buttons to traverse journey's steps + const overlayFooter = ( + setNumberOfCaptions((prevVal) => (val ? prevVal + 1 : prevVal - 1))} + /> + ); + + useEffect(() => { + // This is a hack to get state if image is in full screen, we should refactor + // it once eui image exposes it's full screen state + // we are checking if number of captions are 2, that means + // image is in full screen mode since caption is also rendered on + // full screen image + // we dont want to change image displayed in thumbnail + if (numberOfCaptions === 1 && stepNumber !== initialStepNo) { + setStepNumber(initialStepNo); + } + }, [numberOfCaptions, initialStepNo, stepNumber]); + + return ( +
setIsImagePopoverOpen(true)} + onMouseLeave={() => setIsImagePopoverOpen(false)} + ref={intersectionRef} + > + {imgSrc || screenshotRef ? ( + + ) : ( + + )} +
+ ); +}; + +export const formatCaptionContent = (stepNumber: number, totalSteps?: number) => + i18n.translate('xpack.synthetics.monitor.stepOfSteps', { + defaultMessage: 'Step: {stepNumber} of {totalSteps}', + values: { + stepNumber, + totalSteps, + }, + }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx new file mode 100644 index 0000000000000..12dcd4db95f00 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { CSSProperties } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; +import { getTextColorForMonitorStatus, parseBadgeStatus } from './status_badge'; + +interface Props { + step: JourneyStep; + stepLabels?: string[]; + allStepsLoaded?: boolean; + compactView?: boolean; +} + +export const JourneyStepScreenshotWithLabel = ({ + step, + stepLabels = [], + compactView, + allStepsLoaded, +}: Props) => { + const { euiTheme } = useEuiTheme(); + const status = parseBadgeStatus(step.synthetics.step?.status ?? ''); + const textColor = euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']; + + return ( + + + + + + + {step.synthetics?.step?.name} + + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx new file mode 100644 index 0000000000000..f38084971114b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { render } from '../../../utils/testing'; +import { ScreenshotOverlayFooter, ScreenshotOverlayFooterProps } from './screenshot_overlay_footer'; +import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; +import moment from 'moment'; +import { mockRef } from '../../../utils/testing/__mocks__/screenshot_ref.mock'; + +describe('ScreenshotOverlayFooter', () => { + let defaultProps: ScreenshotOverlayFooterProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption content', + imgSrc: 'http://sample.com/sampleImageSrc.png', + maxSteps: 3, + setStepNumber: jest.fn(), + stepNumber: 2, + label: getShortTimeStamp(moment('2020-11-26T15:28:56.896Z')), + onVisible: jest.fn(), + isLoading: false, + }; + }); + + it('labels prev and next buttons', () => { + const { getByLabelText } = render(); + + expect(getByLabelText('Previous step')); + expect(getByLabelText('Next step')); + }); + + it('increments step number on next click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Next step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(3); + }); + }); + + it('decrements step number on prev click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Previous step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(1); + }); + }); + + it('disables `next` button on final step', () => { + defaultProps.stepNumber = 3; + + const { getByLabelText } = render(); + + // getByLabelText('Next step'); + expect(getByLabelText('Next step')).toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).not.toHaveAttribute('disabled'); + }); + + it('disables `prev` button on final step', () => { + defaultProps.stepNumber = 1; + + const { getByLabelText } = render(); + + expect(getByLabelText('Next step')).not.toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).toHaveAttribute('disabled'); + }); + + it('renders a timestamp', () => { + const { getByText } = render(); + + getByText('Nov 26, 2020 10:28:56 AM'); + }); + + it('renders caption content', () => { + const { getByText } = render(); + + getByText('test caption content'); + }); + + it('renders caption content for screenshot ref data', async () => { + const { getByText } = render( + + ); + + getByText('test caption content'); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.tsx new file mode 100644 index 0000000000000..3a01a74721d40 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.tsx @@ -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 React, { MouseEvent, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiText, + useEuiTheme, + useIsWithinMaxBreakpoint, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; + +export interface ScreenshotOverlayFooterProps { + captionContent: string; + imgSrc?: string; + imgRef?: ScreenshotRefImageData; + maxSteps?: number; + setStepNumber: React.Dispatch>; + stepNumber: number; + label?: string; + onVisible: (val: boolean) => void; + isLoading: boolean; +} + +export const ScreenshotOverlayFooter: React.FC = ({ + captionContent, + imgRef, + imgSrc, + maxSteps, + setStepNumber, + stepNumber, + isLoading, + label, + onVisible, +}) => { + const { euiTheme } = useEuiTheme(); + + useEffect(() => { + onVisible(true); + return () => { + onVisible(false); + }; + // Empty deps to only trigger effect once on init + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const isSmall = useIsWithinMaxBreakpoint('m'); + + return ( +
{ + // we don't want this to be captured by row click which leads to step list page + evt.stopPropagation(); + }} + onKeyDown={(evt) => { + // Just to satisfy ESLint + }} + > +
+ {(imgSrc || imgRef) && ( + + + ) => { + setStepNumber(stepNumber - 1); + evt.preventDefault(); + }} + iconType="arrowLeft" + aria-label={prevAriaLabel} + isLoading={isLoading} + > + {prevAriaLabel} + + + + {captionContent} + + + ) => { + setStepNumber(stepNumber + 1); + evt.stopPropagation(); + }} + iconType="arrowRight" + iconSide="right" + aria-label={nextAriaLabel} + isLoading={isLoading} + > + {nextAriaLabel} + + + + )} + + {label} + +
+
+ ); +}; + +export const prevAriaLabel = i18n.translate('xpack.synthetics.monitor.step.previousStep', { + defaultMessage: 'Previous step', +}); + +export const nextAriaLabel = i18n.translate('xpack.synthetics.monitor.step.nextStep', { + defaultMessage: 'Next step', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx new file mode 100644 index 0000000000000..c1da7e0035097 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx @@ -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 React from 'react'; +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Ping } from '../../../../../../common/runtime_types'; +import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats'; + +export const SinglePingResult = ({ ping, loading }: { ping: Ping; loading: boolean }) => { + const ip = !loading ? ping?.resolve?.ip : undefined; + const durationUs = !loading ? ping?.monitor?.duration?.us : undefined; + const rtt = !loading ? ping?.resolve?.rtt?.us : undefined; + const url = !loading ? ping?.url?.full : undefined; + const responseStatus = !loading ? ping?.http?.response?.status_code : undefined; + + return ( + + IP + {ip} + {DURATION_LABEL} + + {formatTestDuration(durationUs)} + + rtt + {formatTestDuration(rtt)} + URL + {url} + + {responseStatus ? ( + <> + Response status + + {responseStatus} + + + ) : null} + + ); +}; + +const DURATION_LABEL = i18n.translate('xpack.synthetics.monitor.duration.label', { + defaultMessage: 'Duration', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx new file mode 100644 index 0000000000000..a18c0d7a2ca7e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBadge, IconColor, EuiThemeComputed } from '@elastic/eui'; + +type MonitorStatus = 'succeeded' | 'failed' | 'skipped'; +export const StatusBadge = ({ status }: { status: MonitorStatus }) => { + return ( + + {status === 'succeeded' ? COMPLETE_LABEL : status === 'failed' ? FAILED_LABEL : SKIPPED_LABEL} + + ); +}; + +export const parseBadgeStatus = (status: string) => { + switch (status) { + case 'succeeded': + case 'success': + case 'up': + return 'succeeded'; + case 'fail': + case 'failed': + case 'down': + return 'failed'; + case 'skip': + case 'skipped': + return 'skipped'; + default: + return 'skipped'; + } +}; + +export const getBadgeColorForMonitorStatus = (status: MonitorStatus): IconColor => { + return status === 'succeeded' ? 'success' : status === 'failed' ? 'danger' : 'default'; +}; + +export const getTextColorForMonitorStatus = ( + status: MonitorStatus +): keyof EuiThemeComputed['colors'] => { + return status === 'skipped' ? 'disabledText' : 'text'; +}; + +export const COMPLETE_LABEL = i18n.translate('xpack.synthetics.monitorStatus.complete', { + defaultMessage: 'Complete', +}); + +export const FAILED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.failed', { + defaultMessage: 'Failed', +}); + +export const SKIPPED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.skipped', { + defaultMessage: 'Skipped', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.tsx new file mode 100644 index 0000000000000..68f5d919f90e3 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { CSSProperties, useMemo } from 'react'; +import { EuiText, useEuiTheme } from '@elastic/eui'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats'; + +import { parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; + +export const StepDurationText = ({ step }: { step: JourneyStep }) => { + const { euiTheme } = useEuiTheme(); + + const stepDuration = useMemo(() => { + const status = parseBadgeStatus(step.synthetics.step?.status ?? ''); + const color = euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']; + if (status === 'skipped') { + return { text: '-', color }; + } + + return { + text: formatTestDuration(step.synthetics.step?.duration?.us), + color, + }; + }, [euiTheme.colors, step.synthetics.step?.duration?.us, step.synthetics.step?.status]); + + return ( + + {stepDuration.text} + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts new file mode 100644 index 0000000000000..f840658a3dc48 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { + ScreenshotImageBlob, + ScreenshotRefImageData, +} from '../../../../../../common/runtime_types'; +import { getJourneyScreenshot } from '../../../state'; + +type ImageResponse = ScreenshotImageBlob | ScreenshotRefImageData | null; +interface DataResult { + [imgPath: string]: { attempts: number; data: ImageResponse; loading: boolean }; +} + +function getUpdatedState( + prevState: DataResult, + imgPath: string, + increment: boolean, + data?: ImageResponse, + loading?: boolean +) { + const newAttempts = (prevState[imgPath]?.attempts ?? 0) + (increment ? 1 : 0); + const newData = data ?? prevState[imgPath]?.data ?? null; + const newLoading = loading ?? prevState[imgPath]?.loading ?? false; + return { + ...prevState, + [imgPath]: { attempts: newAttempts, data: newData, loading: newLoading }, + }; +} + +export const useRetrieveStepImage = ({ + stepStatus, + hasImage, + hasIntersected, + imgPath, + retryFetchOnRevisit, +}: { + imgPath: string; + stepStatus?: string; + hasImage: boolean; + hasIntersected: boolean; + + /** + * Whether to retry screenshot image fetch on revisit (when intersection change triggers). + * Will only re-fetch if an image fetch wasn't successful in previous attempts. + * Set this to `true` fro "Run Once" / "Test Now" modes + */ + retryFetchOnRevisit: boolean; +}) => { + const [imgState, setImgState] = useState({}); + + const skippedStep = stepStatus === 'skipped'; + + useFetcher(() => { + const hasBeenRetriedBefore = (imgState[imgPath]?.attempts ?? 0) > 0; + const shouldRetry = retryFetchOnRevisit || !hasBeenRetriedBefore; + + if (!skippedStep && hasIntersected && !hasImage && shouldRetry) { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, false, undefined, true); + }); + return getJourneyScreenshot(imgPath) + .then((resp) => { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, true, resp, false); + }); + + return resp; + }) + .catch(() => { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, true, null, false); + }); + }); + } else { + return new Promise((resolve) => resolve(null)); + } + }, [skippedStep, hasIntersected, imgPath, retryFetchOnRevisit]); + + return imgState[imgPath] ?? { data: null, loading: false }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx index 6d7fa2cbc441f..e5d8c8152d68f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx @@ -58,3 +58,7 @@ const MonacoCodeContainer = euiStyled.div` z-index: 0; } `; + +export const JSONEditor = (props: any) => { + return ; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index 83ba909f3063c..644901aadd5c2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -29,6 +29,7 @@ import { EuiLink, EuiTextArea, } from '@elastic/eui'; +import { getDocLinks } from '../../../../../kibana_services'; import { useMonitorName } from '../hooks/use_monitor_name'; import { MonitorTypeRadioGroup } from '../fields/monitor_type_radio_group'; import { @@ -53,6 +54,7 @@ import { ComboBox } from '../fields/combo_box'; import { SourceField } from '../fields/source_field'; import { getDefaultFormFields } from './defaults'; import { validate, validateHeaders, WHOLE_NUMBERS_ONLY, FLOATS_ONLY } from './validation'; +import { JSONEditor } from '../fields/code_editor'; const getScheduleContent = (value: number) => { if (value > 60) { @@ -1006,4 +1008,56 @@ export const FIELD: Record = { required: true, }), }, + [ConfigKey.PLAYWRIGHT_OPTIONS]: { + fieldKey: ConfigKey.PLAYWRIGHT_OPTIONS, + component: JSONEditor, + label: i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.label', { + defaultMessage: 'Playwright options', + }), + helpText: ( + + {i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.helpText', { + defaultMessage: 'Configure Playwright agent with custom options. ', + })} + + {i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.learnMore', { + defaultMessage: 'Learn more', + })} + + + ), + error: i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.error', { + defaultMessage: 'Invalid JSON format', + }), + ariaLabel: i18n.translate( + 'xpack.synthetics.monitorConfig.playwrightOptions.codeEditor.json.ariaLabel', + { + defaultMessage: 'Playwright options JSON code editor', + } + ), + controlled: true, + required: false, + props: ({ + field, + setValue, + }: { + field?: ControllerRenderProps; + setValue: UseFormReturn['setValue']; + }) => ({ + onChange: (json: string) => setValue(ConfigKey.PLAYWRIGHT_OPTIONS, json), + }), + validation: () => ({ + validate: (value) => { + const validateFn = validate[DataStream.BROWSER][ConfigKey.PLAYWRIGHT_OPTIONS]; + if (validateFn) { + return !validateFn({ + [ConfigKey.PLAYWRIGHT_OPTIONS]: value, + }); + } + }, + }), + }, }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index 9f11f2c53c06e..132e3ab0343e1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -103,6 +103,21 @@ export const TCP_ADVANCED = { }, }; +export const BROWSER_ADVANCED = [ + { + title: i18n.translate('xpack.synthetics.monitorConfig.section.syntAgentOptions.title', { + defaultMessage: 'Synthetics agent options', + }), + description: i18n.translate( + 'xpack.synthetics.monitorConfig.section.syntAgentOptions.description', + { + defaultMessage: 'Provide fine-tuned configuration for the synthetics agent.', + } + ), + components: [FIELD[`${ConfigKey.PLAYWRIGHT_OPTIONS}`]], + }, +]; + interface AdvancedFieldGroup { title: string; description: string; @@ -197,6 +212,7 @@ export const FORM_CONFIG: FieldConfig = { FIELD[ConfigKey.NAMESPACE], ], }, + ...BROWSER_ADVANCED, ], }, [FormMonitorType.SINGLE]: { @@ -220,6 +236,7 @@ export const FORM_CONFIG: FieldConfig = { FIELD[ConfigKey.NAMESPACE], ], }, + ...BROWSER_ADVANCED, ], }, [FormMonitorType.ICMP]: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx index de3ce4bc327fd..00330134344c4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx @@ -57,6 +57,21 @@ export const validateTimeout = ({ return parseFloat(timeout) > schedule; }; +export const validJSONFormat = (value: string) => { + let obj; + + try { + obj = JSON.parse(value); + if (!obj || typeof obj !== 'object') { + return false; + } + } catch (e) { + return false; + } + + return true; +}; + // validation functions return true when invalid const validateCommon: ValidationLibrary = { [ConfigKey.SCHEDULE]: ({ [ConfigKey.SCHEDULE]: value }) => { @@ -145,6 +160,8 @@ const validateBrowser: ValidationLibrary = { [ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) => validateThrottleValue(uploadSpeed), [ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true), + [ConfigKey.PLAYWRIGHT_OPTIONS]: ({ [ConfigKey.PLAYWRIGHT_OPTIONS]: playwrightOptions }) => + playwrightOptions ? !validJSONFormat(playwrightOptions) : false, }; export type ValidateDictionary = Record; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx new file mode 100644 index 0000000000000..a6d2070d0e96a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx @@ -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 { useFetcher } from '@kbn/observability-plugin/public'; +import { SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; +import { fetchJourneySteps } from '../../../state'; + +export const useJourneySteps = (checkGroup: string | undefined) => { + const { data, loading } = useFetcher(() => { + if (!checkGroup) { + return Promise.resolve(null); + } + + return fetchJourneySteps({ checkGroup }); + }, [checkGroup]); + + return { data: data as SyntheticsJourneyApiResponse, loading: loading ?? false }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx similarity index 79% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx index 895168657ba1b..779b8d8001ad5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx @@ -7,13 +7,13 @@ import { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { selectSelectedLocationId, setMonitorSummaryLocationAction } from '../../../state'; +import { selectSelectedLocationId, setMonitorDetailsLocationAction } from '../../../state'; import { useUrlParams, useLocations } from '../../../hooks'; export const useSelectedLocation = () => { const [getUrlParams, updateUrlParams] = useUrlParams(); const { locations } = useLocations(); - const selectedLocation = useSelector(selectSelectedLocationId); + const selectedLocationId = useSelector(selectSelectedLocationId); const dispatch = useDispatch(); const { locationId: urlLocationId } = getUrlParams(); @@ -26,10 +26,10 @@ export const useSelectedLocation = () => { } } - if (urlLocationId && selectedLocation !== urlLocationId) { - dispatch(setMonitorSummaryLocationAction(urlLocationId)); + if (urlLocationId && selectedLocationId !== urlLocationId) { + dispatch(setMonitorDetailsLocationAction(urlLocationId)); } - }, [dispatch, updateUrlParams, locations, urlLocationId, selectedLocation]); + }, [dispatch, updateUrlParams, locations, urlLocationId, selectedLocationId]); return useMemo( () => locations.find((loc) => loc.id === urlLocationId) ?? null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx new file mode 100644 index 0000000000000..6e4d972fb445b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { + getMonitorAction, + selectEncryptedSyntheticsSavedMonitors, + selectMonitorListState, + selectorMonitorDetailsState, +} from '../../../state'; + +export const useSelectedMonitor = () => { + const { monitorId } = useParams<{ monitorId: string }>(); + const monitorsList = useSelector(selectEncryptedSyntheticsSavedMonitors); + const { loading: monitorListLoading } = useSelector(selectMonitorListState); + const monitorFromList = useMemo( + () => monitorsList.find((monitor) => monitor.id === monitorId) ?? null, + [monitorId, monitorsList] + ); + + const { syntheticsMonitor, syntheticsMonitorLoading } = useSelector(selectorMonitorDetailsState); + const dispatch = useDispatch(); + + const isMonitorFromListValid = monitorId && monitorFromList && monitorFromList?.id === monitorId; + const isLoadedSyntheticsMonitorValid = + monitorId && syntheticsMonitor && syntheticsMonitor?.id === monitorId; + const availableMonitor = isLoadedSyntheticsMonitorValid + ? syntheticsMonitor + : isMonitorFromListValid + ? monitorFromList + : null; + + useEffect(() => { + if (monitorId && !availableMonitor && !syntheticsMonitorLoading) { + dispatch(getMonitorAction.get({ monitorId })); + } + }, [dispatch, monitorId, availableMonitor, syntheticsMonitorLoading]); + + return { + monitor: availableMonitor, + loading: syntheticsMonitorLoading || monitorListLoading, + }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx similarity index 98% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx index 4c3f4e885bc86..d69cde15f734d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx @@ -16,7 +16,6 @@ import { useSelectedLocation } from './hooks/use_selected_location'; export const MonitorSummaryLastRunInfo = ({ ping }: { ping: Ping }) => { const selectedLocation = useSelectedLocation(); const isBrowserType = ping.monitor.type === 'browser'; - const theme = useTheme(); return ( @@ -26,7 +25,7 @@ export const MonitorSummaryLastRunInfo = ({ ping }: { ping: Ping }) => { {isBrowserType ? SUCCESS_LABEL : UP_LABEL} - ) : ping.monitor.status === 'up' ? ( + ) : ping.monitor.status === 'down' ? ( {isBrowserType ? FAILED_LABEL : DOWN_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx new file mode 100644 index 0000000000000..ee9ba46c561bb --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.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 React, { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { useSelectedMonitor } from './hooks/use_selected_monitor'; +import { useSelectedLocation } from './hooks/use_selected_location'; +import { getMonitorAction, getMonitorRecentPingsAction } from '../../state/monitor_details'; +import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; +export const MonitorDetailsPage = () => { + const { monitor } = useSelectedMonitor(); + + useMonitorListBreadcrumbs([{ text: monitor?.name ?? '' }]); + + const dispatch = useDispatch(); + + const selectedLocation = useSelectedLocation(); + const { monitorId } = useParams<{ monitorId: string }>(); + + useEffect(() => { + dispatch(getMonitorAction.get({ monitorId })); + + if (selectedLocation) { + dispatch(getMonitorRecentPingsAction.get({ monitorId, locationId: selectedLocation.id })); + } + }, [dispatch, monitorId, selectedLocation]); + + return <>; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.tsx new file mode 100644 index 0000000000000..0425ab3ce309e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.tsx @@ -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 React from 'react'; +import { MonitorDetailsTabs } from './monitor_detials_tabs'; + +export const MonitorDetailsPageHeader = () => { + return ; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx new file mode 100644 index 0000000000000..954ecc77c26c0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.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, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useSelectedMonitor } from './hooks/use_selected_monitor'; +import { useSelectedLocation } from './hooks/use_selected_location'; +import { getMonitorRecentPingsAction, selectLatestPing, selectPingsLoading } from '../../state'; +import { MonitorSummaryLastRunInfo } from './last_run_info'; + +export const MonitorDetailsPageTitle = () => { + const dispatch = useDispatch(); + + const latestPing = useSelector(selectLatestPing); + const pingsLoading = useSelector(selectPingsLoading); + + const { monitorId } = useParams<{ monitorId: string }>(); + const { monitor } = useSelectedMonitor(); + const location = useSelectedLocation(); + + useEffect(() => { + const locationId = location?.label; + if (monitorId && locationId) { + dispatch(getMonitorRecentPingsAction.get({ monitorId, locationId })); + } + }, [dispatch, monitorId, location]); + + return ( + + {monitor?.name} + + {pingsLoading || (latestPing && latestPing.monitor.id !== monitorId) ? ( + + ) : latestPing ? ( + + ) : ( + + {i18n.translate('xpack.synthetics.monitorSummary.noLastRunInformationAvailable', { + defaultMessage: 'No last run information available for {location} yet.', + values: { + location: location?.label, + }, + })} + + )} + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx similarity index 83% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx index 725d209c8200e..6667034da5fdf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx @@ -8,11 +8,11 @@ import { EuiIcon, EuiSpacer, EuiTabbedContent } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ErrorsTabContent } from './tabs_content/errors_tab_content'; -import { HistoryTabContent } from './tabs_content/history_tab_content'; -import { SummaryTabContent } from './tabs_content/summary_tab_content'; +import { ErrorsTabContent } from './monitor_errors/monitor_errors'; +import { HistoryTabContent } from './monitor_history/monitor_history'; +import { MonitorSummary } from './monitor_summary/monitor_summary'; -export const MonitorSummaryTabs = () => { +export const MonitorDetailsTabs = () => { const tabs = [ { id: 'summary', @@ -20,7 +20,7 @@ export const MonitorSummaryTabs = () => { content: ( <> - + ), }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/errors_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/errors_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/history_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/history_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_sparklines.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_trend.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_trend.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.tsx new file mode 100644 index 0000000000000..82a3eb3c6b7d8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { useEuiTheme } from '@elastic/eui'; + +export const KpiWrapper: React.FC> = ({ children }) => { + const { euiTheme } = useEuiTheme(); + + const wrapperStyle = css` + border: none; + & > span.euiLoadingSpinner { + margin: ${euiTheme.size.s}; + } + + .legacyMtrVis__container > div { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + `; + + return
{children}
; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx new file mode 100644 index 0000000000000..85adcd7ff3c0c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx @@ -0,0 +1,251 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; +import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; + +import { ConfigKey, DataStream, JourneyStep, Ping } from '../../../../../../common/runtime_types'; +import { + formatTestDuration, + formatTestRunAt, +} from '../../../utils/monitor_test_result/test_time_formats'; +import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context'; + +import { sortPings } from '../../../utils/monitor_test_result/sort_pings'; +import { checkIsStalePing } from '../../../utils/monitor_test_result/check_pings'; +import { selectPingsLoading, selectMonitorRecentPings, selectPingsError } from '../../../state'; +import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; +import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list'; +import { JourneyStepScreenshotContainer } from '../../common/monitor_test_result/journey_step_screenshot_container'; + +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; +import { useJourneySteps } from '../hooks/use_journey_steps'; + +type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us'; + +export const LastTenTestRuns = () => { + const { basePath } = useSyntheticsSettingsContext(); + + const [sortField, setSortField] = useState('timestamp'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const pings = useSelector(selectMonitorRecentPings); + const sortedPings = useMemo(() => { + return sortPings(pings, sortField, sortDirection); + }, [pings, sortField, sortDirection]); + const pingsLoading = useSelector(selectPingsLoading); + const pingsError = useSelector(selectPingsError); + const { monitor } = useSelectedMonitor(); + + const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; + const hasStalePings = checkIsStalePing(monitor, pings?.[0]); + const loading = hasStalePings || pingsLoading; + + const sorting: EuiTableSortingType = { + sort: { + field: sortField as keyof Ping, + direction: sortDirection as 'asc' | 'desc', + }, + }; + + const handleTableChange = ({ page, sort }: Criteria) => { + if (sort !== undefined) { + setSortField(sort.field as SortableField); + setSortDirection(sort.direction); + } + }; + + const columns: Array> = [ + ...((isBrowserMonitor + ? [ + { + align: 'left', + field: 'timestamp', + name: SCREENSHOT_LABEL, + render: (_timestamp: string, item) => , + }, + ] + : []) as Array>), + { + align: 'left', + valign: 'middle', + field: 'timestamp', + name: '@timestamp', + sortable: true, + render: (timestamp: string, ping: Ping) => ( + + ), + }, + { + align: 'left', + valign: 'middle', + field: 'monitor.status', + name: RESULT_LABEL, + sortable: true, + render: (status: string) => , + }, + { + align: 'left', + field: 'error.message', + name: MESSAGE_LABEL, + textOnly: true, + render: (errorMessage: string) => ( + {errorMessage?.length > 0 ? errorMessage : '-'} + ), + }, + { + align: 'right', + valign: 'middle', + field: 'monitor.duration.us', + name: DURATION_LABEL, + sortable: true, + render: (durationUs: number) => {formatTestDuration(durationUs)}, + }, + ]; + + return ( + + + + +

{pings?.length >= 10 ? LAST_10_TEST_RUNS : TEST_RUNS}

+
+
+ + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewHistory', { + defaultMessage: 'View History', + })} + + +
+ +
+ ); +}; + +const JourneyScreenshot = ({ ping }: { ping: Ping }) => { + const { data: stepsData, loading: stepsLoading } = useJourneySteps(ping?.monitor?.check_group); + const stepEnds: JourneyStep[] = (stepsData?.steps ?? []).filter(isStepEnd); + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + const lastSignificantStep = useMemo(() => { + const copy = [...stepEnds]; + // Sort desc by timestamp + copy.sort( + (stepA, stepB) => + Number(new Date(stepB['@timestamp'])) - Number(new Date(stepA['@timestamp'])) + ); + return copy.find( + (stepEnd) => parseBadgeStatus(stepEnd?.synthetics?.step?.status ?? 'skipped') !== 'skipped' + ); + }, [stepEnds]); + + return ( + + ); +}; + +const TestDetailsLink = ({ + isBrowserMonitor, + timestamp, + ping, +}: { + isBrowserMonitor: boolean; + timestamp: string; + ping: Ping; +}) => { + const { euiTheme } = useEuiTheme(); + const { basePath } = useSyntheticsSettingsContext(); + + const timestampText = ( + + {formatTestRunAt(timestamp)} + + ); + + return isBrowserMonitor ? ( + + {timestampText} + + ) : ( + timestampText + ); +}; + +const TEST_RUNS = i18n.translate('xpack.synthetics.monitorDetails.summary.testRuns', { + defaultMessage: 'Test Runs', +}); + +const LAST_10_TEST_RUNS = i18n.translate( + 'xpack.synthetics.monitorDetails.summary.lastTenTestRuns', + { + defaultMessage: 'Last 10 Test Runs', + } +); + +const SCREENSHOT_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.screenshot', { + defaultMessage: 'Screenshot', +}); + +const RESULT_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.result', { + defaultMessage: 'Result', +}); + +const MESSAGE_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.message', { + defaultMessage: 'Message', +}); + +const DURATION_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.duration', { + defaultMessage: 'Duration', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx new file mode 100644 index 0000000000000..355dbfa19499e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; + +import { + ConfigKey, + DataStream, + EncryptedSyntheticsSavedMonitor, + Ping, +} from '../../../../../../common/runtime_types'; +import { checkIsStalePing } from '../../../utils/monitor_test_result/check_pings'; +import { formatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; + +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { selectLatestPing, selectPingsLoading } from '../../../state'; +import { BrowserStepsList } from '../../common/monitor_test_result/browser_steps_list'; +import { SinglePingResult } from '../../common/monitor_test_result/single_ping_result'; +import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; + +import { useJourneySteps } from '../hooks/use_journey_steps'; +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; + +export const LastTestRun = () => { + const { euiTheme } = useEuiTheme(); + const latestPing = useSelector(selectLatestPing); + const pingsLoading = useSelector(selectPingsLoading); + const { monitor } = useSelectedMonitor(); + + const { data: stepsData, loading: stepsLoading } = useJourneySteps( + latestPing?.monitor?.check_group + ); + + const hasStalePings = checkIsStalePing(monitor, latestPing); + const loading = hasStalePings || stepsLoading || pingsLoading; + + return ( + + + {!loading && latestPing?.error ? ( + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewErrorDetails', { + defaultMessage: 'View error details', + })} + + + ) : null} + + + + {monitor?.type === DataStream.BROWSER ? ( + + ) : ( + + )} + + ); +}; + +const PanelHeader = ({ + monitor, + latestPing, + loading, +}: { + monitor: EncryptedSyntheticsSavedMonitor | null; + latestPing: Ping; + loading: boolean; +}) => { + const { euiTheme } = useEuiTheme(); + + const { basePath } = useSyntheticsSettingsContext(); + + const lastRunTimestamp = useMemo( + () => (latestPing?.timestamp ? formatTestRunAt(latestPing?.timestamp) : ''), + [latestPing?.timestamp] + ); + + const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; + + const TitleNode = ( + +

{LAST_TEST_RUN_LABEL}

+
+ ); + + if (loading) { + return ( + <> + + {TitleNode} + + + + + + + {isBrowserMonitor ? : null} + + + ); + } + + if (!latestPing) { + return <>{TitleNode}; + } + + return ( + <> + + {TitleNode} + + 0 ? 'fail' : 'success')} + /> + + + + {lastRunTimestamp} + + + + {isBrowserMonitor ? ( + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewTestRun', { + defaultMessage: 'View test run', + })} + + + ) : null} + + + ); +}; + +const LAST_TEST_RUN_LABEL = i18n.translate( + 'xpack.synthetics.monitorDetails.summary.lastTestRunTitle', + { + defaultMessage: 'Last test run', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/locations_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/locations_status.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/locations_status.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/locations_status.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx similarity index 77% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx index 6209ddb7c5d2e..3d8013c79beff 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { css } from '@emotion/react'; import { EuiDescriptionList, EuiDescriptionListTitle, @@ -13,38 +14,46 @@ import { EuiBadge, EuiSpacer, EuiLink, - EuiLoadingSpinner, + EuiLoadingContent, + useEuiTheme, } from '@elastic/eui'; import { capitalize } from 'lodash'; import { i18n } from '@kbn/i18n'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { MonitorTags } from './monitor_tags'; import { MonitorEnabled } from '../../monitors_page/management/monitor_list_table/monitor_enabled'; import { LocationsStatus } from './locations_status'; -import { - getSyntheticsMonitorAction, - selectMonitorStatus, - syntheticsMonitorSelector, -} from '../../../state/monitor_summary'; +import { getMonitorAction, selectLatestPing } from '../../../state'; import { ConfigKey } from '../../../../../../common/runtime_types'; export const MonitorDetailsPanel = () => { - const { data } = useSelector(selectMonitorStatus); + const { euiTheme } = useEuiTheme(); + const latestPing = useSelector(selectLatestPing); const { monitorId } = useParams<{ monitorId: string }>(); const dispatch = useDispatch(); - const { data: monitor, loading } = useSelector(syntheticsMonitorSelector); + const { monitor, loading } = useSelectedMonitor(); - if (!data) { - return ; + if ( + (latestPing && latestPing?.config_id !== monitorId) || + (monitor && monitor.id !== monitorId) + ) { + return ; } + const wrapperStyle = css` + .euiDescriptionList.euiDescriptionList--column > *, + .euiDescriptionList.euiDescriptionList--responsiveColumn > * { + margin-top: ${euiTheme.size.s}; + } + `; + return ( - +
{ENABLED_LABEL} @@ -55,14 +64,14 @@ export const MonitorDetailsPanel = () => { id={monitorId} monitor={monitor} reloadPage={() => { - dispatch(getSyntheticsMonitorAction.get(monitorId)); + dispatch(getMonitorAction.get({ monitorId })); }} /> )} {MONITOR_TYPE_LABEL} - {capitalize(data.monitor.type)} + {capitalize(monitor?.type)} {FREQUENCY_LABEL} Every 10 mins @@ -72,8 +81,8 @@ export const MonitorDetailsPanel = () => { {URL_LABEL} - - {data.url?.full} + + {latestPing?.url?.full} {TAGS_LABEL} @@ -81,17 +90,10 @@ export const MonitorDetailsPanel = () => { {monitor && } - +
); }; -const Wrapper = euiStyled.div` - .euiDescriptionList.euiDescriptionList--column > *, - .euiDescriptionList.euiDescriptionList--responsiveColumn > * { - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } -`; - const FREQUENCY_LABEL = i18n.translate('xpack.synthetics.management.monitorList.frequency', { defaultMessage: 'Frequency', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx similarity index 89% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index afe940fc06400..0349b3e96cea1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -23,8 +23,10 @@ import { AvailabilityPanel } from './availability_panel'; import { DurationPanel } from './duration_panel'; import { MonitorDetailsPanel } from './monitor_details_panel'; import { AvailabilitySparklines } from './availability_sparklines'; +import { LastTestRun } from './last_test_run'; +import { LastTenTestRuns } from './last_ten_test_runs'; -export const SummaryTabContent = () => { +export const MonitorSummary = () => { const { euiTheme } = useEuiTheme(); return ( @@ -57,7 +59,7 @@ export const SummaryTabContent = () => { {/* TODO: Add error metric and sparkline*/} - + @@ -79,17 +81,19 @@ export const SummaryTabContent = () => { - - {/* TODO: Add status panel*/} - + + {/* /!* TODO: Add status panel*!/ */} + {/* */} - {/* TODO: Add last run panel*/} + + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_tags.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_tags.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_tags.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_tags.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/run_test_manually.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/run_test_manually.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx deleted file mode 100644 index de5871304eccf..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx +++ /dev/null @@ -1,27 +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, { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { getSyntheticsMonitorAction, selectMonitorStatus } from '../../state/monitor_summary'; -import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; -export const MonitorSummaryPage = () => { - const { data } = useSelector(selectMonitorStatus); - - useMonitorListBreadcrumbs([{ text: data?.monitor.name ?? '' }]); - - const dispatch = useDispatch(); - - const { monitorId } = useParams<{ monitorId: string }>(); - - useEffect(() => { - dispatch(getSyntheticsMonitorAction.get(monitorId)); - }, [dispatch, monitorId]); - - return <>; -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx deleted file mode 100644 index 1276de0c32974..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx +++ /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 React from 'react'; -import { useSelector } from 'react-redux'; -import { MonitorSummaryTabs } from './monitor_summary_tabs'; -import { selectMonitorStatus } from '../../state/monitor_summary'; - -export const MonitorSummaryHeaderContent = () => { - const { data } = useSelector(selectMonitorStatus); - - if (!data) { - return <>; - } - - return ( - <> - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx deleted file mode 100644 index 9e295496dd77c..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { MonitorSummaryLastRunInfo } from './last_run_info'; -import { getMonitorStatusAction, selectMonitorStatus } from '../../state'; -import { RunTestManually } from './run_test_manually'; - -export const MonitorSummaryTitle = () => { - const dispatch = useDispatch(); - - const { data } = useSelector(selectMonitorStatus); - - const { monitorId } = useParams<{ monitorId: string }>(); - - useEffect(() => { - dispatch(getMonitorStatusAction.get({ monitorId, dateStart: 'now-30d', dateEnd: 'now' })); - }, [dispatch, monitorId]); - - return ( - - - - {data?.monitor.name} - - {data && } - - - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx deleted file mode 100644 index 4496bc0031364..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx +++ /dev/null @@ -1,20 +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 { euiStyled } from '@kbn/kibana-react-plugin/common'; - -export const KpiWrapper = euiStyled.div` - & .euiLoadingSpinner { - margin: ${({ theme }) => theme.eui.euiSizeS}; - } - - & .legacyMtrVis__container > div { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx index 002b22f48b186..3f3a3552446f2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx @@ -8,15 +8,15 @@ import React, { Fragment } from 'react'; import { EuiEmptyPrompt, EuiPanel, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { IHttpSerializedFetchError } from '../../../../state'; interface EmptyStateErrorProps { - errors: Array>; + errors: IHttpSerializedFetchError[]; } export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error) => error.message && error.message.includes('unauthorized') + (error) => error?.body?.message && error.body.message.includes('unauthorized') ); return ( @@ -47,11 +47,7 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error) => ( -

- {error.body?.message || error.message} -

- ))} + errors.map((error) =>

{error.body?.message}

)}
} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts index 0aabe7b54714a..7ec5fccf3d6e4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts @@ -14,3 +14,4 @@ export * from './use_last_x_checks'; export * from './use_last_50_duration_chart'; export * from './use_location_name'; export * from './use_status_by_location'; +export * from './use_composite_image'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts index 0902481c51a9c..4225f888a09bb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts @@ -68,7 +68,8 @@ export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { const params = useUrlParams()[0](); const kibana = useKibana(); const setBreadcrumbs = kibana.services.chrome?.setBreadcrumbs; - const uptimePath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; + const syntheticsPath = + kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; const observabilityPath = kibana.services.application?.getUrlForApp('observability-overview') ?? ''; const navigate = kibana.services.application?.navigateToUrl; @@ -77,10 +78,10 @@ export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { if (setBreadcrumbs) { setBreadcrumbs( handleBreadcrumbClick( - makeBaseBreadcrumb(uptimePath, observabilityPath, params).concat(extraCrumbs), + makeBaseBreadcrumb(syntheticsPath, observabilityPath, params).concat(extraCrumbs), navigate ) ); } - }, [uptimePath, observabilityPath, extraCrumbs, navigate, params, setBreadcrumbs]); + }, [syntheticsPath, observabilityPath, extraCrumbs, navigate, params, setBreadcrumbs]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx new file mode 100644 index 0000000000000..f77d169dc739e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 redux from 'react-redux'; +import { renderHook } from '@testing-library/react-hooks'; +import { ScreenshotRefImageData, ScreenshotBlockCache } from '../../../../common/runtime_types'; +import { fetchBlocksAction } from '../state'; +import { shouldCompose, useCompositeImage } from './use_composite_image'; +import * as compose from '../utils/monitor_test_result/compose_screenshot_images'; + +const MIME = 'image/jpeg'; + +describe('use composite image', () => { + let imageData: string | undefined; + let imgRef: ScreenshotRefImageData; + let curRef: ScreenshotRefImageData; + let blocks: ScreenshotBlockCache; + + beforeEach(() => { + imgRef = { + stepName: 'step-1', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '123', + monitor: { + check_group: 'check-group', + }, + screenshot_ref: { + width: 100, + height: 200, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + width: 10, + height: 10, + }, + { + hash: 'hash2', + top: 0, + left: 10, + width: 10, + height: 10, + }, + ], + }, + synthetics: { + package_version: 'v1', + step: { index: 0, name: 'first' }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + curRef = { + stepName: 'step-1', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '234', + monitor: { + check_group: 'check-group-2', + }, + screenshot_ref: { + width: 100, + height: 200, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + width: 10, + height: 10, + }, + { + hash: 'hash2', + top: 0, + left: 10, + width: 10, + height: 10, + }, + ], + }, + synthetics: { + package_version: 'v1', + step: { index: 1, name: 'second' }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + blocks = { + hash1: { + id: 'id1', + synthetics: { + blob: 'blob', + blob_mime: MIME, + }, + }, + hash2: { + id: 'id2', + synthetics: { + blob: 'blob', + blob_mime: MIME, + }, + }, + }; + }); + + describe('shouldCompose', () => { + it('returns true if all blocks are loaded and ref is new', () => { + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(true); + }); + + it('returns false if a required block is pending', () => { + blocks.hash2 = { status: 'pending' }; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns false if a required block is missing', () => { + delete blocks.hash2; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns false if imageData is defined and the refs have matching step index/check_group', () => { + imageData = 'blob'; + curRef.ref.screenshotRef.synthetics.step.index = 0; + curRef.ref.screenshotRef.monitor.check_group = 'check-group'; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns true if imageData is defined and the refs have different step names', () => { + imageData = 'blob'; + curRef.ref.screenshotRef.synthetics.step.index = 0; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(true); + }); + }); + + describe('useCompositeImage', () => { + let useDispatchMock: jest.Mock; + let canvasMock: unknown; + let removeChildSpy: jest.Mock; + let selectorSpy: jest.SpyInstance; + let composeSpy: jest.SpyInstance; + + beforeEach(() => { + useDispatchMock = jest.fn(); + removeChildSpy = jest.fn(); + canvasMock = { + parentElement: { + removeChild: removeChildSpy, + }, + toDataURL: jest.fn().mockReturnValue('compose success'), + }; + // @ts-expect-error mocking canvas element for testing + jest.spyOn(document, 'createElement').mockReturnValue(canvasMock); + jest.spyOn(redux, 'useDispatch').mockReturnValue(useDispatchMock); + selectorSpy = jest.spyOn(redux, 'useSelector').mockReturnValue({ blocks }); + composeSpy = jest + .spyOn(compose, 'composeScreenshotRef') + .mockReturnValue(new Promise((r) => r([]))); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('does not compose if all blocks are not loaded', () => { + blocks = {}; + renderHook(() => useCompositeImage(imgRef, jest.fn(), imageData)); + + expect(useDispatchMock).toHaveBeenCalledWith(fetchBlocksAction(['hash1', 'hash2'])); + }); + + it('composes when all required blocks are loaded', async () => { + const onComposeImageSuccess = jest.fn(); + const { waitFor } = renderHook(() => useCompositeImage(imgRef, onComposeImageSuccess)); + + expect(selectorSpy).toHaveBeenCalled(); + expect(composeSpy).toHaveBeenCalledTimes(1); + expect(composeSpy.mock.calls[0][0]).toEqual(imgRef); + expect(composeSpy.mock.calls[0][1]).toBe(canvasMock); + expect(composeSpy.mock.calls[0][2]).toBe(blocks); + + await waitFor( + () => { + expect(onComposeImageSuccess).toHaveBeenCalledTimes(1); + expect(onComposeImageSuccess).toHaveBeenCalledWith('compose success'); + }, + { timeout: 10000 } + ); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts new file mode 100644 index 0000000000000..5cdaffb83fa21 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useDispatch, useSelector } from 'react-redux'; +import React from 'react'; +import { composeScreenshotRef } from '../utils/monitor_test_result/compose_screenshot_images'; +import { + ScreenshotRefImageData, + ScreenshotBlockCache, + StoreScreenshotBlock, +} from '../../../../common/runtime_types'; +import { fetchBlocksAction, isPendingBlock } from '../state'; +import { selectBrowserJourneyState } from '../state'; + +function allBlocksLoaded(blocks: { [key: string]: StoreScreenshotBlock }, hashes: string[]) { + for (const hash of hashes) { + if (!blocks[hash] || isPendingBlock(blocks[hash])) { + return false; + } + } + return true; +} + +/** + * Checks if two refs are the same. If the ref is unchanged, there's no need + * to run the expensive draw procedure. + * + * The key fields here are `step.index` and `check_group`, as there's a 1:1 between + * journey and check group, and each step has a unique index within a journey. + */ +const isNewRef = ( + { + ref: { + screenshotRef: { + synthetics: { + step: { index: indexA }, + }, + monitor: { check_group: checkGroupA }, + }, + }, + }: ScreenshotRefImageData, + { + ref: { + screenshotRef: { + synthetics: { + step: { index: indexB }, + }, + monitor: { check_group: checkGroupB }, + }, + }, + }: ScreenshotRefImageData +): boolean => indexA !== indexB || checkGroupA !== checkGroupB; + +export function shouldCompose( + imageData: string | undefined, + imgRef: ScreenshotRefImageData, + curRef: ScreenshotRefImageData, + blocks: ScreenshotBlockCache +): boolean { + return ( + allBlocksLoaded( + blocks, + imgRef.ref.screenshotRef.screenshot_ref.blocks.map(({ hash }) => hash) + ) && + (typeof imageData === 'undefined' || isNewRef(imgRef, curRef)) + ); +} + +/** + * Assembles the data for a composite image and returns the composite to a callback. + * @param imgRef the data and dimensions for the composite image. + * @param onComposeImageSuccess sends the composited image to this callback. + * @param imageData this is the composited image value, if it is truthy the function will skip the compositing process + */ +export const useCompositeImage = ( + imgRef: ScreenshotRefImageData, + onComposeImageSuccess: React.Dispatch, + imageData?: string +): void => { + const dispatch = useDispatch(); + const { blocks }: { blocks: ScreenshotBlockCache } = useSelector(selectBrowserJourneyState); + + React.useEffect(() => { + dispatch( + fetchBlocksAction(imgRef.ref.screenshotRef.screenshot_ref.blocks.map(({ hash }) => hash)) + ); + }, [dispatch, imgRef.ref.screenshotRef.screenshot_ref.blocks]); + + const [curRef, setCurRef] = React.useState(imgRef); + + React.useEffect(() => { + const canvas = document.createElement('canvas'); + + async function compose() { + await composeScreenshotRef(imgRef, canvas, blocks); + const imgData = canvas.toDataURL('image/jpg', 1.0); + onComposeImageSuccess(imgData); + } + + // if the URL is truthy it means it's already been composed, so there + // is no need to call the function + if (shouldCompose(imageData, imgRef, curRef, blocks)) { + compose(); + setCurRef(imgRef); + } + return () => { + canvas.parentElement?.removeChild(canvas); + }; + }, [blocks, curRef, imageData, imgRef, onComposeImageSuccess]); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 8da37518fbede..3c1081f9f1f04 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -17,9 +17,9 @@ import { useInspectorContext } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page'; import { MonitorEditPage } from './components/monitor_add_edit/monitor_edit_page'; -import { MonitorSummaryHeaderContent } from './components/monitor_summary/monitor_summary_header_content'; -import { MonitorSummaryTitle } from './components/monitor_summary/monitor_summary_title'; -import { MonitorSummaryPage } from './components/monitor_summary/monitor_summary'; +import { MonitorDetailsPageHeader } from './components/monitor_details/monitor_details_page_header'; +import { MonitorDetailsPageTitle } from './components/monitor_details/monitor_details_page_title'; +import { MonitorDetailsPage } from './components/monitor_details/monitor_details_page'; import { GettingStartedPage } from './components/getting_started/getting_started_page'; import { MonitorsPageHeader } from './components/monitors_page/management/page_header/monitors_page_header'; import { OverviewPage } from './components/monitors_page/overview/overview_page'; @@ -78,16 +78,16 @@ const getRoutes = ( }, }, { - title: i18n.translate('xpack.synthetics.monitorSummaryRoute.title', { - defaultMessage: 'Monitor summary | {baseTitle}', + title: i18n.translate('xpack.synthetics.monitorDetails.title', { + defaultMessage: 'Synthetics Monitor Details | {baseTitle}', values: { baseTitle }, }), path: MONITOR_ROUTE, - component: () => , - dataTestSubj: 'syntheticsGettingStartedPage', + component: () => , + dataTestSubj: 'syntheticsMonitorDetailsPage', pageHeader: { - children: , - pageTitle: , + children: , + pageTitle: , // rightSideItems: [], }, }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.ts new file mode 100644 index 0000000000000..3a151ced4246c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.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 { createAction } from '@reduxjs/toolkit'; +import { PutBlocksPayload } from './models'; + +// This action denotes a set of blocks is required +export const fetchBlocksAction = createAction('[BROWSER JOURNEY] FETCH BLOCKS'); + +// This action denotes a request for a set of blocks is in flight +export const setBlockLoadingAction = createAction( + '[BROWSER JOURNEY] SET BLOCKS IN FLIGHT' +); + +// Block data has been received, and should be added to the store +export const putBlocksAction = createAction( + '[BROWSER JOURNEY] PUT SCREENSHOT BLOCKS' +); + +// Updates the total size of the image blob data cached in the store +export const putCacheSize = createAction('[BROWSER JOURNEY] PUT CACHE SIZE'); + +// Keeps track of the most-requested blocks +export const updateHitCountsAction = createAction('[BROWSER JOURNEY] UPDATE HIT COUNTS'); + +// Reduce the cache size to the value in the action payload +export const pruneCacheAction = createAction( + '[BROWSER JOURNEY] PRUNE SCREENSHOT BLOCK CACHE' +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts new file mode 100644 index 0000000000000..a6fd6185af06b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts @@ -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 { apiService } from '../../../../utils/api_service'; +import { + FailedStepsApiResponse, + FailedStepsApiResponseType, + ScreenshotBlockDoc, + ScreenshotImageBlob, + ScreenshotRefImageData, + SyntheticsJourneyApiResponse, + SyntheticsJourneyApiResponseType, + Ping, + PingType, +} from '../../../../../common/runtime_types'; +import { API_URLS } from '../../../../../common/constants'; + +export interface FetchJourneyStepsParams { + checkGroup: string; + syntheticEventTypes?: string[]; +} + +export async function fetchScreenshotBlockSet(params: string[]): Promise { + return apiService.post(API_URLS.JOURNEY_SCREENSHOT_BLOCKS, { + hashes: params, + }); +} + +export async function fetchJourneySteps( + params: FetchJourneyStepsParams +): Promise { + return apiService.get( + API_URLS.JOURNEY.replace('{checkGroup}', params.checkGroup), + { syntheticEventTypes: params.syntheticEventTypes }, + SyntheticsJourneyApiResponseType + ); +} + +export async function fetchJourneysFailedSteps({ + checkGroups, +}: { + checkGroups: string[]; +}): Promise { + return apiService.get(API_URLS.JOURNEY_FAILED_STEPS, { checkGroups }, FailedStepsApiResponseType); +} + +export async function fetchLastSuccessfulCheck({ + monitorId, + timestamp, + stepIndex, + location, +}: { + monitorId: string; + timestamp: string; + stepIndex: number; + location?: string; +}): Promise { + return await apiService.get( + API_URLS.SYNTHETICS_SUCCESSFUL_CHECK, + { + monitorId, + timestamp, + stepIndex, + location, + }, + PingType + ); +} + +export async function getJourneyScreenshot( + imgSrc: string +): Promise { + try { + const imgRequest = new Request(imgSrc); + + const response = await fetch(imgRequest); + + if (response.status !== 200) { + return null; + } + + const contentType = response.headers.get('content-type'); + const stepName = response.headers.get('caption-name'); + const maxSteps = Number(response.headers.get('max-steps') ?? 0); + if (contentType?.indexOf('application/json') !== -1) { + return { + stepName, + maxSteps, + ref: await response.json(), + }; + } else { + return { + stepName, + maxSteps, + src: URL.createObjectURL(await response.blob()), + }; + } + } catch (e) { + return null; + } +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.ts new file mode 100644 index 0000000000000..ad85440ee9708 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Action } from 'redux-actions'; +import { all, call, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'; +import { ScreenshotBlockDoc, ScreenshotBlockCache } from '../../../../../common/runtime_types'; +import { fetchScreenshotBlockSet } from './api'; + +import { + fetchBlocksAction, + setBlockLoadingAction, + pruneCacheAction, + putBlocksAction, + putCacheSize, + updateHitCountsAction, +} from './actions'; + +import { isPendingBlock } from './models'; + +import { selectBrowserJourneyState } from './selectors'; + +export function* browserJourneyEffects() { + yield all([fork(fetchScreenshotBlocks), fork(generateBlockStatsOnPut), fork(pruneBlockCache)]); +} + +function* fetchBlocks(hashes: string[]) { + yield put(setBlockLoadingAction(hashes)); + const blocks: ScreenshotBlockDoc[] = yield call(fetchScreenshotBlockSet, hashes); + yield put(putBlocksAction({ blocks })); +} + +function* fetchScreenshotBlocks() { + /** + * We maintain a list of each hash and how many times it is requested so we can avoid + * subsequent re-requests if the block is dropped due to cache pruning. + */ + yield takeEvery(String(fetchBlocksAction), function* (action: Action) { + if (action.payload.length > 0) { + yield put(updateHitCountsAction(action.payload)); + } + }); + + /** + * We do a short delay to allow multiple item renders to queue up before dispatching + * a fetch to the backend. + */ + yield throttle(20, String(fetchBlocksAction), function* () { + const { blocks }: { blocks: ScreenshotBlockCache } = yield select(selectBrowserJourneyState); + const toFetch = Object.keys(blocks).filter((hash) => { + const block = blocks[hash]; + return isPendingBlock(block) && block.status !== 'loading'; + }); + + if (toFetch.length > 0) { + yield fork(fetchBlocks, toFetch); + } + }); +} + +function* generateBlockStatsOnPut() { + yield takeEvery( + String(putBlocksAction), + function* (action: Action<{ blocks: ScreenshotBlockDoc[] }>) { + const batchSize = action.payload.blocks.reduce((total, cur) => { + return cur.synthetics.blob.length + total; + }, 0); + yield put(putCacheSize(batchSize)); + } + ); +} + +// 4 MB cap for cache size +const MAX_CACHE_SIZE = 4000000; + +function* pruneBlockCache() { + yield takeEvery(String(putCacheSize), function* (_action: Action) { + const { cacheSize }: { cacheSize: number } = yield select(selectBrowserJourneyState); + + if (cacheSize > MAX_CACHE_SIZE) { + yield put(pruneCacheAction(cacheSize - MAX_CACHE_SIZE)); + } + }); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.ts new file mode 100644 index 0000000000000..75b82f989358b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createReducer } from '@reduxjs/toolkit'; + +import { isScreenshotBlockDoc } from '../../../../../common/runtime_types'; + +import type { BrowserJourneyState } from './models'; +import { + pruneCacheAction, + putBlocksAction, + putCacheSize, + updateHitCountsAction, + fetchBlocksAction, + setBlockLoadingAction, +} from './actions'; + +const initialState: BrowserJourneyState = { + blocks: {}, + cacheSize: 0, + hitCount: [], +}; + +export const browserJourneyReducer = createReducer(initialState, (builder) => { + builder + /** + * When removing blocks from the cache, we receive an action with a number. + * The number equates to the desired ceiling size of the cache. We then discard + * blocks, ordered by the least-requested. We continue dropping blocks until + * the newly-pruned size will be less than the ceiling supplied by the action. + */ + .addCase(pruneCacheAction, (state, action) => { + handlePruneAction(state, action.payload); + }) + + /** + * Keep track of the least- and most-requested blocks, so when it is time to + * prune we keep the most commonly-used ones. + */ + .addCase(updateHitCountsAction, (state, action) => { + handleUpdateHitCountsAction(state, action.payload); + }) + + .addCase(putCacheSize, (state, action) => { + state.cacheSize = state.cacheSize + action.payload; + }) + + .addCase(fetchBlocksAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload + // there's no need to overwrite existing blocks because the key + // is either storing a pending req or a cached result + .filter((b) => !state.blocks[b]) + // convert the list of new hashes in the payload to an object that + // will combine with with the existing blocks cache + .reduce( + (acc, cur) => ({ + ...acc, + [cur]: { status: 'pending' }, + }), + {} + ), + }; + }) + + .addCase(setBlockLoadingAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload.reduce( + (acc, cur) => ({ + ...acc, + [cur]: { status: 'loading' }, + }), + {} + ), + }; + }) + + .addCase(putBlocksAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload.blocks.reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), + }; + }); +}); + +function handlePruneAction(state: BrowserJourneyState, pruneSize: number) { + const { blocks, hitCount } = state; + const hashesToPrune: string[] = []; + let sizeToRemove = 0; + let removeIndex = hitCount.length - 1; + while (sizeToRemove < pruneSize && removeIndex >= 0) { + const { hash } = hitCount[removeIndex]; + removeIndex--; + if (!blocks[hash]) continue; + const block = blocks[hash]; + if (isScreenshotBlockDoc(block)) { + sizeToRemove += block.synthetics.blob.length; + hashesToPrune.push(hash); + } + } + for (const hash of hashesToPrune) { + delete blocks[hash]; + } + + state.cacheSize = state.cacheSize - sizeToRemove; + state.hitCount = hitCount.slice(0, removeIndex + 1); +} + +function handleUpdateHitCountsAction(state: BrowserJourneyState, hashes: string[]) { + const newHitCount = [...state.hitCount]; + const hitTime = Date.now(); + hashes.forEach((hash) => { + const countItem = newHitCount.find((item) => item.hash === hash); + if (!countItem) { + newHitCount.push({ hash, hitTime }); + } else { + countItem.hitTime = hitTime; + } + }); + // sorts in descending order + newHitCount.sort((a, b) => b.hitTime - a.hitTime); + + state.hitCount = newHitCount; +} + +export * from './api'; +export * from './models'; +export * from './actions'; +export * from './effects'; +export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.ts new file mode 100644 index 0000000000000..12d6074300c89 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.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 { + PendingBlock, + ScreenshotBlockCache, + ScreenshotBlockDoc, +} from '../../../../../common/runtime_types'; + +export function isPendingBlock(data: unknown): data is PendingBlock { + return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); +} + +export interface CacheHitCount { + hash: string; + hitTime: number; +} + +export interface BrowserJourneyState { + blocks: ScreenshotBlockCache; + cacheSize: number; + hitCount: CacheHitCount[]; +} + +export interface PutBlocksPayload { + blocks: ScreenshotBlockDoc[]; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.ts new file mode 100644 index 0000000000000..eae2632d9ae5a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.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. + */ + +import { SyntheticsAppState } from '../root_reducer'; + +export const selectBrowserJourneyState = (state: SyntheticsAppState) => state.browserJourney; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts index 6076292c34550..727fd0dfcd4c2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts @@ -15,5 +15,6 @@ export * from './index_status'; export * from './synthetics_enablement'; export * from './service_locations'; export * from './monitor_list'; -export * from './monitor_summary'; +export * from './monitor_details'; export * from './overview'; +export * from './browser_journey'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts index d1592e26bf17d..f5351c65d0d6b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { createReducer } from '@reduxjs/toolkit'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions'; @@ -14,7 +14,7 @@ import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './act export interface IndexStatusState { data: StatesIndexStatus | null; loading: boolean; - error: IHttpFetchError | null; + error: IHttpSerializedFetchError | null; } const initialState: IndexStatusState = { @@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getIndexStatusFail, (state, action) => { - state.error = action.payload as IHttpFetchError; + state.error = serializeHttpFetchError(action.payload); state.loading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.ts new file mode 100644 index 0000000000000..a80196275a759 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.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 { createAction } from '@reduxjs/toolkit'; +import { + Ping, + PingsResponse, + EncryptedSyntheticsSavedMonitor, +} from '../../../../../common/runtime_types'; +import { QueryParams } from './api'; +import { createAsyncAction } from '../utils/actions'; + +export const setMonitorDetailsLocationAction = createAction( + '[MONITOR SUMMARY] SET LOCATION' +); + +export const getMonitorStatusAction = createAsyncAction('[MONITOR DETAILS] GET'); + +export const getMonitorAction = createAsyncAction< + { monitorId: string }, + EncryptedSyntheticsSavedMonitor +>('[MONITOR DETAILS] GET MONITOR'); + +export const getMonitorRecentPingsAction = createAsyncAction< + { monitorId: string; locationId: string }, + PingsResponse +>('[MONITOR DETAILS] GET RECENT PINGS'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.ts new file mode 100644 index 0000000000000..f2541b119e56d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.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 { SavedObject } from '@kbn/core/types'; +import { apiService } from '../../../../utils/api_service'; +import { + EncryptedSyntheticsSavedMonitor, + PingsResponse, + PingsResponseType, + SyntheticsMonitor, +} from '../../../../../common/runtime_types'; +import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; + +export interface QueryParams { + monitorId: string; + dateStart: string; + dateEnd: string; +} + +export const fetchMonitorRecentPings = async ({ + monitorId, + locationId, +}: { + monitorId: string; + locationId: string; +}): Promise => { + const from = new Date(0).toISOString(); + const to = new Date().toISOString(); + const locations = JSON.stringify([locationId]); + const sort = 'desc'; + const size = 10; + + return await apiService.get( + SYNTHETICS_API_URLS.PINGS, + { monitorId, from, to, locations, sort, size }, + PingsResponseType + ); +}; + +export const fetchSyntheticsMonitor = async ({ + monitorId, +}: { + monitorId: string; +}): Promise => { + const savedObject = (await apiService.get( + `${API_URLS.SYNTHETICS_MONITORS}/${monitorId}` + )) as SavedObject; + + return { + id: savedObject.id, + ...savedObject.attributes, + updated_at: savedObject.updated_at, + } as EncryptedSyntheticsSavedMonitor; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.ts new file mode 100644 index 0000000000000..1b1b686400d88 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.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 { takeLeading } from 'redux-saga/effects'; +import { fetchEffectFactory } from '../utils/fetch_effect'; +import { getMonitorRecentPingsAction, getMonitorAction } from './actions'; +import { fetchSyntheticsMonitor, fetchMonitorRecentPings } from './api'; + +export function* fetchSyntheticsMonitorEffect() { + yield takeLeading( + getMonitorRecentPingsAction.get, + fetchEffectFactory( + fetchMonitorRecentPings, + getMonitorRecentPingsAction.success, + getMonitorRecentPingsAction.fail + ) + ); + + yield takeLeading( + getMonitorAction.get, + fetchEffectFactory(fetchSyntheticsMonitor, getMonitorAction.success, getMonitorAction.fail) + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts new file mode 100644 index 0000000000000..a2d9379df778e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.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 { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; +import { createReducer } from '@reduxjs/toolkit'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { + getMonitorRecentPingsAction, + setMonitorDetailsLocationAction, + getMonitorAction, +} from './actions'; +import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../../common/runtime_types'; + +export interface MonitorDetailsState { + pings: Ping[]; + loading: boolean; + syntheticsMonitorLoading: boolean; + syntheticsMonitor: EncryptedSyntheticsSavedMonitor | null; + error: IHttpSerializedFetchError | null; + selectedLocationId: string | null; +} + +const initialState: MonitorDetailsState = { + pings: [], + loading: false, + syntheticsMonitor: null, + syntheticsMonitorLoading: false, + error: null, + selectedLocationId: null, +}; + +export const monitorDetailsReducer = createReducer(initialState, (builder) => { + builder + .addCase(setMonitorDetailsLocationAction, (state, action) => { + state.selectedLocationId = action.payload; + }) + + .addCase(getMonitorRecentPingsAction.get, (state) => { + state.loading = true; + }) + .addCase(getMonitorRecentPingsAction.success, (state, action) => { + state.pings = action.payload.pings; + state.loading = false; + }) + .addCase(getMonitorRecentPingsAction.fail, (state, action) => { + state.error = serializeHttpFetchError(action.payload as IHttpFetchError); + state.loading = false; + }) + + .addCase(getMonitorAction.get, (state) => { + state.syntheticsMonitorLoading = true; + }) + .addCase(getMonitorAction.success, (state, action) => { + state.syntheticsMonitor = action.payload; + state.syntheticsMonitorLoading = false; + }) + .addCase(getMonitorAction.fail, (state, action) => { + state.error = serializeHttpFetchError(action.payload as IHttpFetchError); + state.syntheticsMonitorLoading = false; + }); +}); + +export * from './actions'; +export * from './effects'; +export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts similarity index 53% rename from x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts index d361024e839f2..5c6ba75e8cd6d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts @@ -8,13 +8,19 @@ import { createSelector } from 'reselect'; import type { SyntheticsAppState } from '../root_reducer'; -const getState = (appState: SyntheticsAppState) => appState.monitorStatus; +const getState = (appState: SyntheticsAppState) => appState.monitorDetails; + +export const selectorMonitorDetailsState = createSelector(getState, (state) => state); export const selectSelectedLocationId = createSelector( getState, (state) => state.selectedLocationId ); -export const selectMonitorStatus = createSelector(getState, (state) => state); +export const selectLatestPing = createSelector(getState, (state) => state.pings?.[0] ?? null); + +export const selectPingsLoading = createSelector(getState, (state) => state.loading); + +export const selectMonitorRecentPings = createSelector(getState, (state) => state.pings); -export const syntheticsMonitorSelector = (state: SyntheticsAppState) => state.syntheticsMonitor; +export const selectPingsError = createSelector(getState, (state) => state.error); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts deleted file mode 100644 index 9595243a53bc3..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts +++ /dev/null @@ -1,21 +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 { createAction } from '@reduxjs/toolkit'; -import { Ping, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { QueryParams } from './api'; -import { createAsyncAction } from '../utils/actions'; - -export const setMonitorSummaryLocationAction = createAction( - '[MONITOR SUMMARY] SET LOCATION' -); - -export const getMonitorStatusAction = createAsyncAction('[MONITOR SUMMARY] GET'); - -export const getSyntheticsMonitorAction = createAsyncAction( - 'fetchSyntheticsMonitorAction' -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.ts deleted file mode 100644 index af01acf97592d..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { SavedObject } from '@kbn/core/types'; -import { apiService } from '../../../../utils/api_service'; -import { Ping, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; - -export interface QueryParams { - monitorId: string; - dateStart: string; - dateEnd: string; -} - -export const fetchMonitorStatus = async (params: QueryParams): Promise => { - return await apiService.get(SYNTHETICS_API_URLS.MONITOR_STATUS, { ...params }); -}; - -export const fetchSyntheticsMonitor = async (monitorId: string): Promise => { - const { attributes } = (await apiService.get( - `${API_URLS.SYNTHETICS_MONITORS}/${monitorId}` - )) as SavedObject; - - return attributes; -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.ts deleted file mode 100644 index 9a1b52e1e24df..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { takeLeading } from 'redux-saga/effects'; -import { fetchEffectFactory } from '../utils/fetch_effect'; -import { getMonitorStatusAction, getSyntheticsMonitorAction } from './actions'; -import { fetchMonitorStatus, fetchSyntheticsMonitor } from './api'; - -export function* fetchMonitorStatusEffect() { - yield takeLeading( - getMonitorStatusAction.get, - fetchEffectFactory( - fetchMonitorStatus, - getMonitorStatusAction.success, - getMonitorStatusAction.fail - ) - ); -} - -export function* fetchSyntheticsMonitorEffect() { - yield takeLeading( - getSyntheticsMonitorAction.get, - fetchEffectFactory( - fetchSyntheticsMonitor, - getSyntheticsMonitorAction.success, - getSyntheticsMonitorAction.fail - ) - ); -} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts deleted file mode 100644 index 04941c6286211..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts +++ /dev/null @@ -1,48 +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 { createReducer } from '@reduxjs/toolkit'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; - -import { getMonitorStatusAction, setMonitorSummaryLocationAction } from './actions'; -import type { Ping } from '../../../../../common/runtime_types'; - -export interface MonitorSummaryState { - data: Ping | null; - loading: boolean; - error: IHttpFetchError | null; - selectedLocationId: string | null; -} - -const initialState: MonitorSummaryState = { - data: null, - loading: false, - error: null, - selectedLocationId: null, -}; - -export const monitorStatusReducer = createReducer(initialState, (builder) => { - builder - .addCase(setMonitorSummaryLocationAction, (state, action) => { - state.selectedLocationId = action.payload; - }) - .addCase(getMonitorStatusAction.get, (state) => { - state.loading = true; - }) - .addCase(getMonitorStatusAction.success, (state, action) => { - state.data = action.payload; - state.loading = false; - }) - .addCase(getMonitorStatusAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError; - state.loading = false; - }); -}); - -export * from './actions'; -export * from './effects'; -export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.ts deleted file mode 100644 index 6fa133842f4fc..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.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 { createReducer } from '@reduxjs/toolkit'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; - -import type { SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { getSyntheticsMonitorAction } from './actions'; - -export interface SyntheticsMonitorState { - data: SyntheticsMonitor | null; - loading: boolean; - error: IHttpFetchError | null; -} - -const initialState: SyntheticsMonitorState = { - data: null, - loading: false, - error: null, -}; - -export const syntheticsMonitorReducer = createReducer(initialState, (builder) => { - builder - .addCase(getSyntheticsMonitorAction.get, (state) => { - state.loading = true; - }) - .addCase(getSyntheticsMonitorAction.success, (state, action) => { - state.data = action.payload; - state.loading = false; - }) - .addCase(getSyntheticsMonitorAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError; - state.loading = false; - }); -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index 389b21ea5ea1b..1345f6c2b4c39 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -6,12 +6,13 @@ */ import { all, fork } from 'redux-saga/effects'; -import { fetchMonitorStatusEffect, fetchSyntheticsMonitorEffect } from './monitor_summary'; +import { fetchSyntheticsMonitorEffect } from './monitor_details'; import { fetchIndexStatusEffect } from './index_status'; import { fetchSyntheticsEnablementEffect } from './synthetics_enablement'; import { fetchMonitorListEffect, upsertMonitorEffect } from './monitor_list'; import { fetchMonitorOverviewEffect, quietFetchOverviewEffect } from './overview'; import { fetchServiceLocationsEffect } from './service_locations'; +import { browserJourneyEffects } from './browser_journey'; export const rootEffect = function* root(): Generator { yield all([ @@ -20,9 +21,9 @@ export const rootEffect = function* root(): Generator { fork(upsertMonitorEffect), fork(fetchServiceLocationsEffect), fork(fetchMonitorListEffect), - fork(fetchMonitorStatusEffect), fork(fetchSyntheticsMonitorEffect), fork(fetchMonitorOverviewEffect), fork(quietFetchOverviewEffect), + fork(browserJourneyEffects), ]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts index 34e036bf6fac4..c83605ffad1f8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts @@ -7,17 +7,15 @@ import { combineReducers } from '@reduxjs/toolkit'; -import { - syntheticsMonitorReducer, - SyntheticsMonitorState, -} from './monitor_summary/synthetics_montior_reducer'; -import { monitorStatusReducer, MonitorSummaryState } from './monitor_summary'; +import { monitorDetailsReducer, MonitorDetailsState } from './monitor_details'; import { uiReducer, UiState } from './ui'; import { indexStatusReducer, IndexStatusState } from './index_status'; import { syntheticsEnablementReducer, SyntheticsEnablementState } from './synthetics_enablement'; import { monitorListReducer, MonitorListState } from './monitor_list'; import { serviceLocationsReducer, ServiceLocationsState } from './service_locations'; import { monitorOverviewReducer, MonitorOverviewState } from './overview'; +import { BrowserJourneyState } from './browser_journey/models'; +import { browserJourneyReducer } from './browser_journey'; export interface SyntheticsAppState { ui: UiState; @@ -25,9 +23,9 @@ export interface SyntheticsAppState { syntheticsEnablement: SyntheticsEnablementState; monitorList: MonitorListState; serviceLocations: ServiceLocationsState; - monitorStatus: MonitorSummaryState; - syntheticsMonitor: SyntheticsMonitorState; + monitorDetails: MonitorDetailsState; overview: MonitorOverviewState; + browserJourney: BrowserJourneyState; } export const rootReducer = combineReducers({ @@ -36,7 +34,7 @@ export const rootReducer = combineReducers({ syntheticsEnablement: syntheticsEnablementReducer, monitorList: monitorListReducer, serviceLocations: serviceLocationsReducer, - monitorStatus: monitorStatusReducer, - syntheticsMonitor: syntheticsMonitorReducer, + monitorDetails: monitorDetailsReducer, overview: monitorOverviewReducer, + browserJourney: browserJourneyReducer, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts index 34c5a021c5c03..5c296eedb79f9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts @@ -18,6 +18,18 @@ export interface IHttpSerializedFetchError { } export const serializeHttpFetchError = (error: IHttpFetchError): IHttpSerializedFetchError => { + if (error.name && !error.body) { + return { + name: error.name, + body: { + error: error.toString(), + message: error.message, + statusCode: undefined, + }, + requestUrl: error?.request?.url, + }; + } + const body = error.body as { error: string; message: string; statusCode: number }; return { name: error.name, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/check_pings.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/check_pings.ts new file mode 100644 index 0000000000000..043aefbac819b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/check_pings.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 { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../../common/runtime_types'; + +/** + * Checks if the loaded/cached pings are of the current selected monitors + */ +export function checkIsStalePing( + monitor: EncryptedSyntheticsSavedMonitor | null, + ping: Ping | undefined +) { + if (!monitor?.id || !ping?.monitor?.id) { + return true; + } + + return monitor.id !== ping.monitor.id && monitor.id !== ping.config_id; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.test.ts new file mode 100644 index 0000000000000..8d91afc3c9937 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../../../common/runtime_types'; +import { composeScreenshotRef } from './compose_screenshot_images'; + +describe('composeScreenshotRef', () => { + let getContextMock: jest.Mock; + let drawImageMock: jest.Mock; + let ref: ScreenshotRefImageData; + let contextMock: unknown; + + beforeEach(() => { + drawImageMock = jest.fn(); + contextMock = { + drawImage: drawImageMock, + }; + getContextMock = jest.fn().mockReturnValue(contextMock); + ref = { + stepName: 'step', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '123', + monitor: { check_group: 'check-group' }, + screenshot_ref: { + blocks: [ + { + hash: '123', + top: 0, + left: 0, + width: 10, + height: 10, + }, + ], + height: 100, + width: 100, + }, + synthetics: { + package_version: 'v1', + step: { + name: 'step-name', + index: 0, + }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + }); + + it('throws error when blob does not exist', async () => { + try { + // @ts-expect-error incomplete invocation for test + await composeScreenshotRef(ref, { getContext: getContextMock }, {}); + } catch (e: any) { + expect(e).toMatchInlineSnapshot( + `[Error: Error processing image. Expected image data with hash 123 is missing]` + ); + expect(getContextMock).toHaveBeenCalled(); + expect(getContextMock.mock.calls[0][0]).toBe('2d'); + expect(getContextMock.mock.calls[0][1]).toEqual({ alpha: false }); + } + }); + + it('throws error when block is pending', async () => { + try { + await composeScreenshotRef( + ref, + // @ts-expect-error incomplete invocation for test + { getContext: getContextMock }, + { '123': { status: 'pending' } } + ); + } catch (e: any) { + expect(e).toMatchInlineSnapshot( + `[Error: Error processing image. Expected image data with hash 123 is missing]` + ); + expect(getContextMock).toHaveBeenCalled(); + expect(getContextMock.mock.calls[0][0]).toBe('2d'); + expect(getContextMock.mock.calls[0][1]).toEqual({ alpha: false }); + } + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.ts new file mode 100644 index 0000000000000..ed9f4836d9b88 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + isScreenshotBlockDoc, + ScreenshotRefImageData, + ScreenshotBlockCache, +} from '../../../../../common/runtime_types'; + +/** + * Draws image fragments on a canvas. + * @param data Contains overall image size, fragment dimensions, and the blobs of image data to render. + * @param canvas A canvas to use for the rendering. + * @returns A promise that will resolve when the final draw operation completes. + */ +export async function composeScreenshotRef( + data: ScreenshotRefImageData, + canvas: HTMLCanvasElement, + blocks: ScreenshotBlockCache +) { + const { + ref: { screenshotRef }, + } = data; + + const ctx = canvas.getContext('2d', { alpha: false }); + + canvas.width = screenshotRef.screenshot_ref.width; + canvas.height = screenshotRef.screenshot_ref.height; + + /** + * We need to treat each operation as an async task, otherwise we will race between drawing image + * chunks and extracting the final data URL from the canvas; without this, the image could be blank or incomplete. + */ + const drawOperations: Array> = []; + + for (const { hash, top, left, width, height } of screenshotRef.screenshot_ref.blocks) { + drawOperations.push( + new Promise((resolve, reject) => { + const img = new Image(); + const blob = blocks[hash]; + if (!blob || !isScreenshotBlockDoc(blob)) { + reject(Error(`Error processing image. Expected image data with hash ${hash} is missing`)); + } else { + img.onload = () => { + ctx?.drawImage(img, left, top, width, height); + resolve(); + }; + img.src = `data:image/jpg;base64,${blob.synthetics.blob}`; + } + }) + ); + } + + // once all `draw` operations finish, caller can extract img string + return Promise.all(drawOperations); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.ts new file mode 100644 index 0000000000000..7922e046dfbaf --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.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 { get as getProp } from 'lodash'; +import { Ping } from '../../../../../common/runtime_types'; + +export function sortPings(pings: Ping[], sortField: string, sortDirection: 'asc' | 'desc') { + const toSort = [...pings]; + toSort.sort((a, b) => { + let propA = getProp(a, sortField) ?? null; + let propB = getProp(b, sortField) ?? null; + + if (propA === null || propB === null) { + return 0; + } + + if (sortField === 'timestamp') { + propA = new Date(propA); + propB = new Date(propB); + } + + if (sortField === 'monitor.status') { + propA = propA === 'up' ? -1 : 1; + propB = propB === 'up' ? -1 : 1; + } + + if (typeof propA === 'string') { + return sortDirection === 'asc' ? propA.localeCompare(propB) : propB.localeCompare(propA); + } + + return sortDirection === 'asc' ? propA - propB : propB - propA; + }); + + return toSort; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts new file mode 100644 index 0000000000000..005872224b08e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { i18n } from '@kbn/i18n'; + +/** + * Formats the microseconds (µ) into either milliseconds (ms) or seconds (s) based on the duration value + * @param us {number} duration value in microseconds + */ +export const formatTestDuration = (us?: number) => { + const microSecs = us ?? 0; + const secs = microSecs / (1000 * 1000); + if (secs >= 1) { + return `${secs.toFixed(1)} s`; + } + + return `${(microSecs / 1000).toFixed(0)} ms`; +}; + +export function formatTestRunAt(timestamp: string) { + const stampedMoment = moment(timestamp); + const startOfToday = moment().startOf('day'); + const startOfYesterday = moment().add(-1, 'day'); + + const dateStr = + stampedMoment > startOfToday + ? `${TODAY_LABEL}` + : stampedMoment > startOfYesterday + ? `${YESTERDAY_LABEL}` + : `${stampedMoment.format('ll')} `; + + const timeStr = stampedMoment.format('HH:mm:ss'); + + return `${dateStr} @ ${timeStr}`; +} + +const TODAY_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.today', { + defaultMessage: 'Today', +}); + +const YESTERDAY_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.yesterday', { + defaultMessage: 'Yesterday', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.ts new file mode 100644 index 0000000000000..c9c4d022b869d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.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 moment from 'moment'; +import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../common/constants'; + +export const parseTimestamp = (tsValue: string): moment.Moment => { + let parsed = Date.parse(tsValue); + if (isNaN(parsed)) { + parsed = parseInt(tsValue, 10); + } + return moment(parsed); +}; + +export const getShortTimeStamp = (timeStamp: moment.Moment, relative = false) => { + if (relative) { + const prevLocale: string = moment.locale() ?? 'en'; + + const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE; + + if (!shortLocale) { + moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE); + } + + let shortTimestamp; + if (typeof (timeStamp as unknown) === 'string') { + shortTimestamp = parseTimestamp(timeStamp as unknown as string).fromNow(); + } else { + shortTimestamp = timeStamp.fromNow(); + } + + // Reset it so, it doesn't impact other part of the app + moment.locale(prevLocale); + return shortTimestamp; + } else { + if (moment().diff(timeStamp, 'd') >= 1) { + return timeStamp.format('ll LTS'); + } + return timeStamp.format('LTS'); + } +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts new file mode 100644 index 0000000000000..f704c3309c1f9 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; + +export const mockRef: ScreenshotRefImageData = { + maxSteps: 1, + stepName: 'load homepage', + ref: { + screenshotRef: { + '@timestamp': '2021-06-08T19:42:30.257Z', + synthetics: { + package_version: '1.0.0-beta.2', + step: { name: 'load homepage', index: 1 }, + type: 'step/screenshot_ref', + }, + screenshot_ref: { + blocks: [ + { + top: 0, + left: 0, + width: 160, + hash: 'd518801fc523cf02727cd520f556c4113b3098c7', + height: 90, + }, + { + top: 0, + left: 160, + width: 160, + hash: 'fa90345d5d7b05b1601e9ee645e663bc358869e0', + height: 90, + }, + ], + width: 1280, + height: 720, + }, + monitor: { check_group: 'a567cc7a-c891-11eb-bdf9-3e22fb19bf97' }, + }, + }, +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index a91c95f89abc8..57ce5e39a8dbd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -8,8 +8,11 @@ import { SyntheticsAppState } from '../../../state/root_reducer'; import { ConfigKey, + DataStream, DEFAULT_THROTTLING, LocationStatus, + ScheduleUnit, + SourceType, } from '../../../../../../common/runtime_types'; /** @@ -93,15 +96,255 @@ export const mockState: SyntheticsAppState = { loading: false, }, syntheticsEnablement: { loading: false, error: null, enablement: null }, - monitorStatus: { - data: null, - loading: false, - error: null, - selectedLocationId: null, - }, - syntheticsMonitor: { - data: null, + monitorDetails: getMonitorDetailsMockSlice(), + browserJourney: getBrowserJourneyMockSlice(), +}; + +function getBrowserJourneyMockSlice() { + return { + blocks: { + '4bae236101175ae7746cb922f4c511083af4fbcd': { + id: '4bae236101175ae7746cb922f4c511083af4fbcd', + synthetics: { + blob: '/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABaAKADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//9k=', + blob_mime: 'image/jpeg', + }, + }, + ec95c047e2e05a27598451fdaa7f24db973eb933: { + id: 'ec95c047e2e05a27598451fdaa7f24db973eb933', + synthetics: { + blob: '/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABaAKADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMI/8QAGhABAAMBAQEAAAAAAAAAAAAAAAECAwQRIf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDNfT0bdXTr0dWum3RtedNNdLTa17TPs2mZ+zMz99TAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/2Q==', + blob_mime: 'image/jpeg', + }, + }, + }, + cacheSize: 0, + hitCount: [ + { hash: '4bae236101175ae7746cb922f4c511083af4fbcd', hitTime: 1658682270849 }, + { hash: 'ec95c047e2e05a27598451fdaa7f24db973eb933', hitTime: 1658682270849 }, + ], + }; +} + +function getMonitorDetailsMockSlice() { + return { + pings: [ + { + summary: { up: 1, down: 0 }, + agent: { + name: 'cron-b010e1cc9518984e-27644714-4pd4h', + id: 'f8721d90-5aec-4815-a6f1-f4d4a6fb7482', + type: 'heartbeat', + ephemeral_id: 'd6a60494-5e52-418f-922b-8e90f0b4013c', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 269722 }, + origin: SourceType.UI, + name: 'One pixel monitor', + check_group: '051aba1c-0b74-11ed-9f0e-ba4e6fa109d5', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + timespan: { lt: '2022-07-24T17:24:06.094Z', gte: '2022-07-24T17:14:06.094Z' }, + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'cron-b010e1cc9518984e-27644714-4pd4h', + ip: ['10.1.11.162'], + mac: ['ba:4e:6f:a1:09:d5'], + }, + '@timestamp': '2022-07-24T17:14:05.079Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:14:07Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:14:05.079Z', + docId: 'AkYzMYIBqL6WCtugsFck', + }, + { + summary: { up: 1, down: 0 }, + agent: { + name: 'cron-b010e1cc9518984e-27644704-zs98t', + id: 'a9620214-591d-48e7-9e5d-10b7a9fb1a03', + type: 'heartbeat', + ephemeral_id: 'c5110885-81b4-4e9a-8747-690d19fbd225', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 227326 }, + origin: SourceType.UI, + name: 'One pixel monitor', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + check_group: '9eb87e53-0b72-11ed-b34f-aa618b4334ae', + timespan: { lt: '2022-07-24T17:14:05.020Z', gte: '2022-07-24T17:04:05.020Z' }, + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'cron-b010e1cc9518984e-27644704-zs98t', + ip: ['10.1.9.133'], + mac: ['aa:61:8b:43:34:ae'], + }, + '@timestamp': '2022-07-24T17:04:03.769Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:04:06Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:04:03.769Z', + docId: 'mkYqMYIBqL6WCtughFUq', + }, + { + summary: { up: 1, down: 0 }, + agent: { + name: 'job-b010e1cc9518984e-dkw5k', + id: 'e3a4e3a8-bdd1-44fe-86f5-e451b80f80c5', + type: 'heartbeat', + ephemeral_id: 'f41a13ab-a85d-4614-89c0-8dbad6a32868', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 207700 }, + origin: SourceType.UI, + name: 'One pixel monitor', + timespan: { lt: '2022-07-24T17:11:49.702Z', gte: '2022-07-24T17:01:49.702Z' }, + check_group: '4e00ac5a-0b72-11ed-a97e-5203642c687d', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'job-b010e1cc9518984e-dkw5k', + ip: ['10.1.9.132'], + mac: ['52:03:64:2c:68:7d'], + }, + '@timestamp': '2022-07-24T17:01:48.326Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:01:50Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:01:48.326Z', + docId: 'kUYoMYIBqL6WCtugc1We', + }, + ], loading: false, + syntheticsMonitor: { + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + type: DataStream.BROWSER, + enabled: true, + schedule: { unit: ScheduleUnit.MINUTES, number: '10' }, + 'service.name': '', + tags: [], + timeout: null, + name: 'One pixel monitor', + locations: [{ isServiceManaged: true, id: 'us_central' }], + namespace: 'default', + origin: SourceType.UI, + journey_id: '', + project_id: '', + playwright_options: '', + __ui: { + script_source: { is_generated_script: false, file_name: '' }, + is_zip_url_tls_enabled: false, + is_tls_enabled: false, + }, + params: '', + 'url.port': null, + 'source.inline.script': + "step('Goto one pixel image', async () => {\\n await page.goto('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');\\n});", + 'source.project.content': '', + 'source.zip_url.url': '', + 'source.zip_url.username': '', + 'source.zip_url.password': '', + 'source.zip_url.folder': '', + 'source.zip_url.proxy_url': '', + urls: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + screenshots: 'on', + synthetics_args: [], + 'filter_journeys.match': '', + 'filter_journeys.tags': [], + ignore_https_errors: false, + 'throttling.is_enabled': true, + 'throttling.download_speed': '5', + 'throttling.upload_speed': '3', + 'throttling.latency': '20', + 'throttling.config': '5d/3u/20l', + 'ssl.certificate_authorities': '', + 'ssl.certificate': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.verification_mode': 'full', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + revision: 1, + updated_at: '2022-07-24T17:15:46.342Z', + }, + syntheticsMonitorLoading: false, error: null, - }, -}; + selectedLocationId: 'us_central', + }; +} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts similarity index 76% rename from x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts index 64bc0776b8207..1e2aafff28dca 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { ScreenshotRefImageData } from '../../../../common/runtime_types/ping/synthetics'; -import * as composeScreenshotImages from '../../hooks/use_composite_image'; +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; +import * as composeScreenshotImages from '../../../hooks/use_composite_image'; jest .spyOn(composeScreenshotImages, 'useCompositeImage') diff --git a/x-pack/plugins/synthetics/public/kibana_services.ts b/x-pack/plugins/synthetics/public/kibana_services.ts new file mode 100644 index 0000000000000..eb413b0260fb1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/kibana_services.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 type { CoreStart } from '@kbn/core/public'; + +let coreStart: CoreStart; +export function setStartServices(core: CoreStart) { + coreStart = core; +} + +export const getDocLinks = () => coreStart.docLinks; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx index 6a429d98756af..3e2225af63a1a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx @@ -13,8 +13,8 @@ import { render } from '../../../../../lib/helper/rtl_helpers'; import * as observabilityPublic from '@kbn/observability-plugin/public'; import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; import moment from 'moment'; -import '../../../../../lib/__mocks__/use_composite_image.mock'; -import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; +import '../../../../../lib/__mocks__/legacy_use_composite_image.mock'; +import { mockRef } from '../../../../../lib/__mocks__/legacy_screenshot_ref.mock'; jest.mock('@kbn/observability-plugin/public'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx index d52c9ab0c896a..18df82473468b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx @@ -11,7 +11,7 @@ import { render } from '../../../../../lib/helper/rtl_helpers'; import { StepImageCaption, StepImageCaptionProps } from './step_image_caption'; import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; import moment from 'moment'; -import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; +import { mockRef } from '../../../../../lib/__mocks__/legacy_screenshot_ref.mock'; describe('StepImageCaption', () => { let defaultProps: StepImageCaptionProps; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx index f0e69395f6075..0cae34a05e35b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useContext, useState, useEffect } from 'react'; +import React, { useCallback, useContext, useState, useEffect, useRef } from 'react'; import { useParams, Redirect } from 'react-router-dom'; import { EuiFlexGroup, @@ -38,6 +38,11 @@ import { monitorManagementListSelector } from '../../../state/selectors'; import { kibanaService } from '../../../state/kibana_service'; +import { + PRIVATE_AVAILABLE_LABEL, + TEST_SCHEDULED_LABEL, +} from '../../overview/monitor_list/translations'; + export interface ActionBarProps { monitor: SyntheticsMonitor; isValid: boolean; @@ -63,9 +68,11 @@ export const ActionBar = ({ const [isSaving, setIsSaving] = useState(false); const [isSuccessful, setIsSuccessful] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(undefined); + const mouseMoveTimeoutIds = useRef<[number, number]>([0, 0]); const isReadOnly = monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; const hasServiceManagedLocation = monitor.locations?.some((loc) => loc.isServiceManaged); + const isOnlyPrivateLocations = !locations.some((loc) => loc.isServiceManaged); const { data, status } = useFetcher(() => { if (!isSaving || !isValid) { @@ -139,11 +146,14 @@ export const ActionBar = ({ {!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL} + {onTestNow && ( {/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */} onTestNow()} - onMouseEnter={() => { - setIsPopoverOpen(true); + onMouseOver={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + if (mouseMoveTimeoutIds.current[0] === 0) { + mouseMoveTimeoutIds.current[0] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[1]); + setIsPopoverOpen(true); + }, 250) as unknown as number; + } }} - onMouseLeave={() => { - setIsPopoverOpen(false); + onMouseOut={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + mouseMoveTimeoutIds.current[1] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[0]); + setIsPopoverOpen(false); + mouseMoveTimeoutIds.current = [0, 0]; + }, 100) as unknown as number; }} > {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} @@ -167,7 +190,13 @@ export const ActionBar = ({ isOpen={isPopoverOpen} > -

{TEST_NOW_DESCRIPTION}

+

+ {isTestRunInProgress + ? TEST_SCHEDULED_LABEL + : isOnlyPrivateLocations || (isValid && !hasServiceManagedLocation) + ? PRIVATE_AVAILABLE_LABEL + : TEST_NOW_DESCRIPTION} +

diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts index 916ca8c00b972..098154f4e20ce 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts @@ -22,6 +22,10 @@ export function useRunOnceErrors({ }) { const [locationErrors, setLocationErrors] = useState([]); const [runOnceServiceError, setRunOnceServiceError] = useState(null); + const publicLocations = useMemo( + () => (locations ?? []).filter((loc) => loc.isServiceManaged), + [locations] + ); useEffect(() => { setLocationErrors([]); @@ -43,16 +47,16 @@ export function useRunOnceErrors({ }, [serviceError]); const locationsById: Record = useMemo( - () => (locations as Locations).reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), - [locations] + () => (publicLocations as Locations).reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), + [publicLocations] ); const expectPings = - locations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; + publicLocations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; const hasBlockingError = !!runOnceServiceError || - (locationErrors?.length && locationErrors?.length === locations.length); + (locationErrors?.length && locationErrors?.length === publicLocations.length); const errorMessages = useMemo(() => { if (hasBlockingError) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx index 288cd7232a3d5..f8ed9cb99fb7c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -9,9 +9,11 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; import { useUptimeDataView } from '../../../../hooks'; +import { ClientPluginsStart } from '../../../../../plugin'; interface Props { query: string; @@ -20,6 +22,19 @@ interface Props { export const AlertQueryBar = ({ query = '', onChange }: Props) => { const dataView = useUptimeDataView(); + const { services } = useKibana(); + + const { + appName, + notifications, + http, + docLinks, + uiSettings, + data, + unifiedSearch, + storage, + usageCollection, + } = services; const [inputVal, setInputVal] = useState(query); @@ -53,6 +68,17 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { placeholder={i18n.translate('xpack.synthetics.alerts.searchPlaceholder.kql', { defaultMessage: 'Filter using kql syntax', })} + appName={appName} + deps={{ + unifiedSearch, + data, + storage, + notifications, + http, + docLinks, + uiSettings, + usageCollection, + }} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx index 425d881e0141e..17e8047ac64c7 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx @@ -6,12 +6,12 @@ */ import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Ping } from '../../../../../../common/runtime_types'; import { testNowMonitorAction } from '../../../../state/actions'; import { testNowRunSelector, TestRunStats } from '../../../../state/reducers/test_now_runs'; +import * as labels from '../translations'; export const TestNowColumn = ({ monitorId, @@ -28,7 +28,7 @@ export const TestNowColumn = ({ if (selectedMonitor.monitor.fleet_managed) { return ( - + <>-- ); @@ -36,7 +36,7 @@ export const TestNowColumn = ({ if (!configId) { return ( - + <>-- ); @@ -54,45 +54,13 @@ export const TestNowColumn = ({ } return ( - + testNowClick()} isDisabled={!isTestNowCompleted} - aria-label={TEST_NOW_ARIA_LABEL} + aria-label={labels.TEST_NOW_ARIA_LABEL} /> ); }; - -export const TEST_NOW_ARIA_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.AriaLabel', - { - defaultMessage: 'CLick to run test now', - } -); - -export const TEST_NOW_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available', - { - defaultMessage: 'Test now is only available for monitors added via Monitor Management.', - } -); - -export const PRIVATE_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available.private', - { - defaultMessage: `You can't currently test monitors running on private locations on demand.`, - } -); - -export const TEST_NOW_LABEL = i18n.translate('xpack.synthetics.monitorList.testNow.label', { - defaultMessage: 'Test now', -}); - -export const TEST_SCHEDULED_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.scheduled', - { - defaultMessage: 'Test is already scheduled', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts index 402846b16c875..2f3bac51ce887 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts @@ -83,3 +83,35 @@ export const STATUS_ALERT_COLUMN = i18n.translate( export const TEST_NOW_COLUMN = i18n.translate('xpack.synthetics.monitorList.testNow.label', { defaultMessage: 'Test now', }); + +export const TEST_NOW_AVAILABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.available', + { + defaultMessage: 'Test now is only available for monitors added via Monitor Management.', + } +); + +export const TEST_SCHEDULED_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.scheduled', + { + defaultMessage: 'Test is already scheduled', + } +); + +export const PRIVATE_AVAILABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.available.private', + { + defaultMessage: `You can't currently test monitors running on private locations on demand.`, + } +); + +export const TEST_NOW_ARIA_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.AriaLabel', + { + defaultMessage: 'Click to run test now', + } +); + +export const TEST_NOW_LABEL = i18n.translate('xpack.synthetics.monitorList.testNow.label', { + defaultMessage: 'Test now', +}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx index 3784bc58b76b2..5ca63c2a758e6 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx @@ -9,9 +9,11 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem } from '@elastic/eui'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; import { useGetUrlParams, useUptimeDataView } from '../../../hooks'; +import { ClientPluginsStart } from '../../../../plugin'; const SYNTAX_STORAGE = 'uptime:queryBarSyntax'; @@ -32,7 +34,19 @@ export const isValidKuery = (query: string) => { export const QueryBar = () => { const { search: urlValue } = useGetUrlParams(); + const { services } = useKibana(); + const { + appName, + notifications, + http, + docLinks, + uiSettings, + data, + unifiedSearch, + storage, + usageCollection, + } = services; const { query, setQuery, submitImmediately } = useQueryBar(); const dataView = useUptimeDataView(); @@ -78,6 +92,17 @@ export const QueryBar = () => { query.language === SyntaxType.kuery ? KQL_PLACE_HOLDER : SIMPLE_SEARCH_PLACEHOLDER } isInvalid={isInValid()} + appName={appName} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx index 83e1a4e938650..cbfd48e31788b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { StepScreenshotDisplay } from './step_screenshot_display'; import { render } from '../../lib/helper/rtl_helpers'; import * as observabilityPublic from '@kbn/observability-plugin/public'; -import '../../lib/__mocks__/use_composite_image.mock'; -import { mockRef } from '../../lib/__mocks__/screenshot_ref.mock'; +import '../../lib/__mocks__/legacy_use_composite_image.mock'; +import { mockRef } from '../../lib/__mocks__/legacy_screenshot_ref.mock'; jest.mock('@kbn/observability-plugin/public'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx index 50fc366f50dbe..d26342aca54c5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx @@ -7,8 +7,7 @@ import * as redux from 'react-redux'; import { renderHook } from '@testing-library/react-hooks'; -import { ScreenshotRefImageData } from '../../../common/runtime_types'; -import { ScreenshotBlockCache } from '../state/reducers/synthetics'; +import { ScreenshotRefImageData, ScreenshotBlockCache } from '../../../common/runtime_types'; import { shouldCompose, useCompositeImage } from './use_composite_image'; import * as compose from '../lib/helper/compose_screenshot_images'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts index ca783bdd290c4..9978a6c920d77 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts @@ -8,13 +8,13 @@ import { useDispatch, useSelector } from 'react-redux'; import React from 'react'; import { composeScreenshotRef } from '../lib/helper/compose_screenshot_images'; -import { ScreenshotRefImageData } from '../../../common/runtime_types/ping/synthetics'; import { - fetchBlocksAction, - isPendingBlock, + ScreenshotRefImageData, ScreenshotBlockCache, StoreScreenshotBlock, -} from '../state/reducers/synthetics'; + isPendingBlock, +} from '../../../common/runtime_types'; +import { fetchBlocksAction } from '../state/reducers/synthetics'; import { syntheticsSelector } from '../state/selectors'; function allBlocksLoaded(blocks: { [key: string]: StoreScreenshotBlock }, hashes: string[]) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/screenshot_ref.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_screenshot_ref.mock.ts similarity index 100% rename from x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/screenshot_ref.mock.ts rename to x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_screenshot_ref.mock.ts diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.ts new file mode 100644 index 0000000000000..01a2e75b9a5ff --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.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 { ScreenshotRefImageData } from '../../../../common/runtime_types'; +import * as composeScreenshotImages from '../../hooks/use_composite_image'; + +jest + .spyOn(composeScreenshotImages, 'useCompositeImage') + .mockImplementation( + ( + _imgRef: ScreenshotRefImageData, + callback: React.Dispatch, + url?: string + ) => { + if (!url) { + callback('img src'); + } + } + ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts index 86c7a001b95ab..ea9593ee1b0b7 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts @@ -8,8 +8,8 @@ import { isScreenshotBlockDoc, ScreenshotRefImageData, -} from '../../../../common/runtime_types/ping/synthetics'; -import { ScreenshotBlockCache } from '../../state/reducers/synthetics'; + ScreenshotBlockCache, +} from '../../../../common/runtime_types'; /** * Draws image fragments on a canvas. diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts index 02857fafb69a9..67ac37e4b101d 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts @@ -35,7 +35,7 @@ export const setMonitor = async ({ id?: string; }): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => { if (id) { - return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); + return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); } else { return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor, undefined, { preserve_namespace: true, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts index 6ffbeb6978f75..9c020db333bb9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts @@ -7,16 +7,18 @@ import { Action } from 'redux-actions'; import { call, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'; -import { ScreenshotBlockDoc } from '../../../../common/runtime_types/ping/synthetics'; +import { + ScreenshotBlockDoc, + ScreenshotBlockCache, + isPendingBlock, +} from '../../../../common/runtime_types'; import { fetchScreenshotBlockSet } from '../api/journey'; import { fetchBlocksAction, setBlockLoadingAction, - isPendingBlock, pruneCacheAction, putBlocksAction, putCacheSize, - ScreenshotBlockCache, updateHitCountsAction, } from '../reducers/synthetics'; import { syntheticsSelector } from '../selectors'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts index 06d738d01b42f..1e38c89dc8208 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { isPendingBlock } from '../../../../common/runtime_types'; import { fetchBlocksAction, - isPendingBlock, pruneCacheAction, setBlockLoadingAction, putBlocksAction, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts index 2a0cf7188a9e8..c523e72b64977 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts @@ -9,20 +9,9 @@ import { createAction, handleActions, Action } from 'redux-actions'; import { isScreenshotBlockDoc, ScreenshotBlockDoc, + ScreenshotBlockCache, } from '../../../../common/runtime_types/ping/synthetics'; -export interface PendingBlock { - status: 'pending' | 'loading'; -} - -export function isPendingBlock(data: unknown): data is PendingBlock { - return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); -} -export type StoreScreenshotBlock = ScreenshotBlockDoc | PendingBlock; -export interface ScreenshotBlockCache { - [hash: string]: StoreScreenshotBlock; -} - export interface CacheHitCount { hash: string; hitTime: number; diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 8557f954e713b..f795523cf0fbb 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -41,6 +41,8 @@ import { CasesUiStart } from '@kbn/cases-plugin/public'; import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; import { OVERVIEW_ROUTE } from '../common/constants/ui'; import { @@ -55,6 +57,7 @@ import { } from './legacy_uptime/lib/alert_types'; import { monitorDetailNavigatorParams } from './apps/locators/monitor_detail'; import { editMonitorNavigatorParams } from './apps/locators/edit_monitor'; +import { setStartServices } from './kibana_services'; export interface ClientPluginsSetup { home?: HomePublicPluginSetup; @@ -79,6 +82,13 @@ export interface ClientPluginsStart { dataViews: DataViewsPublicPluginStart; spaces: SpacesPluginStart; cloud?: CloudStart; + appName: string; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; + usageCollection: UsageCollectionStart; } export interface UptimePluginServices extends Partial { @@ -228,6 +238,7 @@ export class UptimePlugin public start(start: CoreStart, plugins: ClientPluginsStart): void { if (plugins.fleet) { const { registerExtension } = plugins.fleet; + setStartServices(start); registerExtension({ package: 'synthetics', diff --git a/x-pack/plugins/synthetics/server/common/pings/query_pings.ts b/x-pack/plugins/synthetics/server/common/pings/query_pings.ts new file mode 100644 index 0000000000000..b6d1b42923928 --- /dev/null +++ b/x-pack/plugins/synthetics/server/common/pings/query_pings.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { UMElasticsearchQueryFn } from '../../legacy_uptime/lib/adapters/framework'; +import { + GetPingsParams, + HttpResponseBody, + PingsResponse, + Ping, +} from '../../../common/runtime_types'; + +const DEFAULT_PAGE_SIZE = 25; + +/** + * This branch of filtering is used for monitors of type `browser`. This monitor + * type represents an unbounded set of steps, with each `check_group` representing + * a distinct journey. The document containing the `summary` field is indexed last, and + * contains the data necessary for querying a journey. + * + * Because of this, when querying for "pings", it is important that we treat `browser` summary + * checks as the "ping" we want. Without this filtering, we will receive >= N pings for a journey + * of N steps, because an individual step may also contain multiple documents. + */ +const REMOVE_NON_SUMMARY_BROWSER_CHECKS = { + must_not: [ + { + bool: { + filter: [ + { + term: { + 'monitor.type': 'browser', + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'summary', + }, + }, + ], + }, + }, + ], + }, + }, + ], +}; + +function isStringArray(value: unknown): value is string[] { + if (!Array.isArray(value)) return false; + // are all array items strings + if (!value.some((s) => typeof s !== 'string')) return true; + throw Error('Excluded locations can only be strings'); +} + +export const queryPings: UMElasticsearchQueryFn = async ({ + uptimeEsClient, + dateRange: { from, to }, + index, + monitorId, + status, + sort, + size: sizeParam, + locations, + excludedLocations, +}) => { + const size = sizeParam ?? DEFAULT_PAGE_SIZE; + + const searchBody = { + size, + ...(index ? { from: index * size } : {}), + query: { + bool: { + filter: [ + { range: { '@timestamp': { gte: from, lte: to } } }, + ...(monitorId ? [{ term: { config_id: monitorId } }] : []), + ...(status ? [{ term: { 'monitor.status': status } }] : []), + ] as QueryDslQueryContainer[], + ...REMOVE_NON_SUMMARY_BROWSER_CHECKS, + }, + }, + sort: [{ '@timestamp': { order: (sort ?? 'desc') as 'asc' | 'desc' } }], + ...((locations ?? []).length > 0 + ? { post_filter: { terms: { 'observer.geo.name': locations as unknown as string[] } } } + : {}), + }; + + // if there are excluded locations, add a clause to the query's filter + const excludedLocationsArray: unknown = excludedLocations && JSON.parse(excludedLocations); + if (isStringArray(excludedLocationsArray) && excludedLocationsArray.length > 0) { + searchBody.query.bool.filter.push({ + bool: { + must_not: [ + { + terms: { + 'observer.geo.name': excludedLocationsArray, + }, + }, + ], + }, + }); + } + + const { + body: { + hits: { hits, total }, + }, + } = await uptimeEsClient.search({ body: searchBody }); + + const pings: Ping[] = hits.map((doc: any) => { + const { _id, _source } = doc; + // Calculate here the length of the content string in bytes, this is easier than in client JS, where + // we don't have access to Buffer.byteLength. There are some hacky ways to do this in the + // client but this is cleaner. + const httpBody: HttpResponseBody | undefined = _source?.http?.response?.body; + if (httpBody && httpBody.content) { + httpBody.content_bytes = Buffer.byteLength(httpBody.content); + } + + return { ..._source, timestamp: _source['@timestamp'], docId: _id }; + }); + + return { + total: total.value, + pings, + }; +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts index 1de6df8e8dfd9..954fa1d76e113 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ScreenshotBlockDoc } from '../../../../common/runtime_types/ping/synthetics'; +import { ScreenshotBlockDoc } from '../../../../common/runtime_types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; interface ScreenshotBlockResultType { diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts index cd72875cf85fd..2e9be1ddf3f74 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts @@ -12,7 +12,7 @@ import { API_URLS } from '../../../../common/constants'; export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: API_URLS.JOURNEY_CREATE, + path: API_URLS.JOURNEY, validate: { params: schema.object({ checkGroup: schema.string(), diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 4190943a1acf2..6e60de0848706 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { createGetMonitorStatusRoute } from './monitor_summary/monitor_status'; import { getAPIKeySyntheticsRoute } from './monitor_cruds/get_api_key'; import { getServiceLocationsRoute } from './synthetics_service/get_service_locations'; import { deleteSyntheticsMonitorRoute } from './monitor_cruds/delete_monitor'; @@ -26,6 +25,7 @@ import { installIndexTemplatesRoute } from './synthetics_service/install_index_t import { editSyntheticsMonitorRoute } from './monitor_cruds/edit_monitor'; import { addSyntheticsMonitorRoute } from './monitor_cruds/add_monitor'; import { addSyntheticsProjectMonitorRoute } from './monitor_cruds/add_monitor_project'; +import { syntheticsGetPingsRoute } from './pings'; import { SyntheticsRestApiRouteFactory, SyntheticsStreamingRouteFactory, @@ -47,7 +47,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ testNowMonitorRoute, getServiceAllowedRoute, getAPIKeySyntheticsRoute, - createGetMonitorStatusRoute, + syntheticsGetPingsRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts new file mode 100644 index 0000000000000..5c28bdab9d3d1 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.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 { + SavedObjectsUpdateResponse, + SavedObject, + SavedObjectsClientContract, + KibanaRequest, +} from '@kbn/core/server'; +import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; +import { + MonitorFields, + EncryptedSyntheticsMonitor, + SyntheticsMonitorWithSecrets, + SyntheticsMonitor, + ConfigKey, + PrivateLocation, +} from '../../../../common/runtime_types'; +import { syntheticsMonitorType } from '../../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { + sendTelemetryEvents, + formatTelemetryUpdateEvent, +} from '../../telemetry/monitor_upgrade_sender'; +import type { UptimeServerSetup } from '../../../legacy_uptime/lib/adapters/framework'; + +// Simplify return promise type and type it with runtime_types + +export const syncEditedMonitorBulk = async ({ + server, + request, + spaceId, + monitorsToUpdate, + savedObjectsClient, + privateLocations, + syntheticsMonitorClient, +}: { + monitorsToUpdate: Array<{ + normalizedMonitor: SyntheticsMonitor; + monitorWithRevision: SyntheticsMonitorWithSecrets; + previousMonitor: SavedObject; + decryptedPreviousMonitor: SavedObject; + }>; + server: UptimeServerSetup; + syntheticsMonitorClient: SyntheticsMonitorClient; + savedObjectsClient: SavedObjectsClientContract; + request: KibanaRequest; + privateLocations: PrivateLocation[]; + spaceId: string; +}) => { + let savedObjectsSuccessful = false; + let syncSuccessful = false; + + try { + async function updateSavedObjects() { + try { + const editedSOPromise = await savedObjectsClient.bulkUpdate( + monitorsToUpdate.map(({ previousMonitor, monitorWithRevision }) => ({ + type: syntheticsMonitorType, + id: previousMonitor.id, + attributes: monitorWithRevision, + })) + ); + savedObjectsSuccessful = true; + return editedSOPromise; + } catch (e) { + savedObjectsSuccessful = false; + } + } + + async function syncUpdatedMonitors() { + try { + const editSyncPromise = await syntheticsMonitorClient.editMonitors( + monitorsToUpdate.map(({ normalizedMonitor, previousMonitor }) => ({ + monitor: normalizedMonitor as MonitorFields, + id: previousMonitor.id, + previousMonitor, + })), + request, + savedObjectsClient, + privateLocations, + spaceId + ); + syncSuccessful = true; + return editSyncPromise; + } catch (e) { + syncSuccessful = false; + } + } + + const [editedMonitorSavedObjects, errors] = await Promise.all([ + updateSavedObjects(), + syncUpdatedMonitors(), + ]); + + monitorsToUpdate.forEach(({ normalizedMonitor, previousMonitor }) => { + const editedMonitorSavedObject = editedMonitorSavedObjects?.saved_objects.find( + (obj) => obj.id === previousMonitor.id + ); + + sendTelemetryEvents( + server.logger, + server.telemetry, + formatTelemetryUpdateEvent( + editedMonitorSavedObject as SavedObjectsUpdateResponse, + previousMonitor, + server.kibanaVersion, + Boolean((normalizedMonitor as MonitorFields)[ConfigKey.SOURCE_INLINE]), + errors + ) + ); + }); + + return { errors, editedMonitors: editedMonitorSavedObjects?.saved_objects }; + } catch (e) { + server.logger.error(`Unable to update Synthetics monitors `); + + if (!syncSuccessful && savedObjectsSuccessful) { + await savedObjectsClient.bulkUpdate( + monitorsToUpdate.map(({ previousMonitor, decryptedPreviousMonitor }) => ({ + type: syntheticsMonitorType, + id: previousMonitor.id, + attributes: decryptedPreviousMonitor.attributes, + })) + ); + } + + throw e; + } +}; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts index d0cb2fe02d4fa..9f4492ce2dc70 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts @@ -49,6 +49,7 @@ describe('syncEditedMonitor', () => { }, packagePolicyService: { get: jest.fn().mockReturnValue({}), + getByIDs: jest.fn().mockReturnValue([]), buildPackagePolicyFromPackage: jest.fn().mockReturnValue({}), }, }, @@ -79,7 +80,9 @@ describe('syncEditedMonitor', () => { const previousMonitor = { id: 'saved-obj-id', - attributes: { name: editedMonitor.name }, + attributes: { name: editedMonitor.name, locations: [] } as any, + type: 'synthetics-monitor', + references: [], } as SavedObject; const syntheticsService = new SyntheticsService(serverMock); @@ -104,9 +107,11 @@ describe('syncEditedMonitor', () => { }); expect(syntheticsService.editConfig).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'saved-obj-id', - }) + expect.arrayContaining([ + expect.objectContaining({ + id: 'saved-obj-id', + }), + ]) ); expect(serverMock.authSavedObjectsClient?.update).toHaveBeenCalledWith( diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index 5dc77816f4aa3..a16a1ba7089e5 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -13,6 +13,7 @@ import { KibanaRequest, } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { getSyntheticsPrivateLocations } from '../../legacy_uptime/lib/saved_objects/private_locations'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { MonitorFields, @@ -153,11 +154,13 @@ export const syncEditedMonitor = async ({ monitorWithRevision ); - const editSyncPromise = syntheticsMonitorClient.editMonitor( - normalizedMonitor as MonitorFields, - previousMonitor.id, + const allPrivateLocations = await getSyntheticsPrivateLocations(savedObjectsClient); + + const editSyncPromise = syntheticsMonitorClient.editMonitors( + [{ monitor: normalizedMonitor as MonitorFields, id: previousMonitor.id, previousMonitor }], request, savedObjectsClient, + allPrivateLocations, spaceId ); diff --git a/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.ts b/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.ts deleted file mode 100644 index 885bd60bf452a..0000000000000 --- a/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.ts +++ /dev/null @@ -1,82 +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 { schema, TypeOf } from '@kbn/config-schema'; -import { UMServerLibs } from '../../legacy_uptime/uptime_server'; -import { syntheticsMonitorType } from '../../../common/types/saved_objects'; -import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; -import { SYNTHETICS_API_URLS } from '../../../common/constants'; -import { ConfigKey, MonitorFields } from '../../../common/runtime_types'; - -const queryParams = schema.object({ - monitorId: schema.string(), - dateStart: schema.string(), - dateEnd: schema.string(), -}); - -type QueryParams = TypeOf; - -export const createGetMonitorStatusRoute: SyntheticsRestApiRouteFactory = (libs: UMServerLibs) => ({ - method: 'GET', - path: SYNTHETICS_API_URLS.MONITOR_STATUS, - validate: { - query: queryParams, - }, - handler: async ({ uptimeEsClient, request, server, savedObjectsClient }): Promise => { - const { monitorId, dateStart, dateEnd } = request.query as QueryParams; - - const latestMonitor = await libs.requests.getLatestMonitor({ - uptimeEsClient, - monitorId, - dateStart, - dateEnd, - }); - - if (latestMonitor.docId) { - return latestMonitor; - } - - if (!server.savedObjectsClient) { - return null; - } - - try { - const { - saved_objects: [monitorSavedObject], - } = await savedObjectsClient.find({ - type: syntheticsMonitorType, - perPage: 1, - page: 1, - filter: `${syntheticsMonitorType}.id: "${syntheticsMonitorType}:${monitorId}" OR ${syntheticsMonitorType}.attributes.${ConfigKey.CUSTOM_HEARTBEAT_ID}: "${monitorId}"`, - }); - - if (!monitorSavedObject) { - return null; - } - - const { - [ConfigKey.URLS]: url, - [ConfigKey.NAME]: name, - [ConfigKey.HOSTS]: host, - [ConfigKey.MONITOR_TYPE]: type, - } = monitorSavedObject.attributes as Partial; - - return { - url: { - full: url || host, - }, - monitor: { - name, - type, - id: monitorSavedObject.id, - }, - }; - } catch (e) { - server.logger.error(e); - } - }, -}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/get_pings.ts b/x-pack/plugins/synthetics/server/routes/pings/get_pings.ts new file mode 100644 index 0000000000000..e94c928caed53 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/get_pings.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 { schema } from '@kbn/config-schema'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { UMServerLibs } from '../../legacy_uptime/lib/lib'; +import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types'; +import { queryPings } from '../../common/pings/query_pings'; + +export const syntheticsGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.PINGS, + validate: { + query: schema.object({ + from: schema.string(), + to: schema.string(), + locations: schema.maybe(schema.string()), + excludedLocations: schema.maybe(schema.string()), + monitorId: schema.maybe(schema.string()), + index: schema.maybe(schema.number()), + size: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + status: schema.maybe(schema.string()), + }), + }, + handler: async ({ uptimeEsClient, request, response }): Promise => { + const { from, to, index, monitorId, status, sort, size, locations, excludedLocations } = + request.query; + + return await queryPings({ + uptimeEsClient, + dateRange: { from, to }, + index, + monitorId, + status, + sort, + size, + locations: locations ? JSON.parse(locations) : [], + excludedLocations, + }); + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/index.ts b/x-pack/plugins/synthetics/server/routes/pings/index.ts new file mode 100644 index 0000000000000..7bc2a27c155bb --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/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 { syntheticsGetPingsRoute } from './get_pings'; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index e8a012f6cf23a..8dc6688a70f3f 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -123,7 +123,7 @@ describe('SyntheticsPrivateLocation', () => { [true, 'Unable to create Synthetics package policy for private location'], [ false, - 'Unable to update Synthetics package policy for monitor Test Monitor. Fleet write permissions are needed to use Synthetics private locations.', + 'Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.', ], ])('throws errors for edit monitor', async (writeIntegrationPolicies, error) => { const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ @@ -137,10 +137,11 @@ describe('SyntheticsPrivateLocation', () => { }); try { - await syntheticsPrivateLocation.editMonitor( - testConfig, + await syntheticsPrivateLocation.editMonitors( + [testConfig], {} as unknown as KibanaRequest, savedObjectsClientMock, + [mockPrivateLocation], 'test-space' ); } catch (e) { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index c694f2aa7567e..f51f9e479929e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -43,13 +43,13 @@ export class SyntheticsPrivateLocation { return `${config.id}-${locId}-${spaceId}`; } - async generateNewPolicy( + generateNewPolicy( config: HeartbeatConfig, privateLocation: PrivateLocation, savedObjectsClient: SavedObjectsClientContract, newPolicyTemplate: NewPackagePolicy, spaceId: string - ): Promise { + ): NewPackagePolicy | null { if (!savedObjectsClient) { throw new Error('Could not find savedObjectsClient'); } @@ -128,7 +128,7 @@ export class SyntheticsPrivateLocation { ); } - const newPolicy = await this.generateNewPolicy( + const newPolicy = this.generateNewPolicy( config, location, savedObjectsClient, @@ -163,83 +163,106 @@ export class SyntheticsPrivateLocation { } } - async editMonitor( - config: HeartbeatConfig, + async editMonitors( + configs: HeartbeatConfig[], request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, + allPrivateLocations: PrivateLocation[], spaceId: string ) { await this.checkPermissions( request, - `Unable to update Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - }. Fleet write permissions are needed to use Synthetics private locations.` + `Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` ); - const { locations } = config; - - const allPrivateLocations = await getSyntheticsPrivateLocations(savedObjectsClient); - const newPolicyTemplate = await this.buildNewPolicy(savedObjectsClient); if (!newPolicyTemplate) { throw new Error(`Unable to create Synthetics package policy for private location`); } - const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); + const policiesToUpdate: Array = []; + const policiesToCreate: NewPackagePolicyWithId[] = []; + const policiesToDelete: string[] = []; - for (const privateLocation of allPrivateLocations) { - const hasLocation = monitorPrivateLocations?.some((loc) => loc.id === privateLocation.id); - const currId = this.getPolicyId(config, privateLocation, spaceId); - const hasPolicy = await this.getMonitor(currId, savedObjectsClient); - try { - if (hasLocation) { - const newPolicy = await this.generateNewPolicy( - config, - privateLocation, - savedObjectsClient, - newPolicyTemplate, - spaceId - ); + const existingPolicies = await this.getExistingPolicies( + configs, + allPrivateLocations, + savedObjectsClient, + spaceId + ); - if (!newPolicy) { - throw new Error( - `Unable to ${ - hasPolicy ? 'update' : 'create' - } Synthetics package policy for private location ${privateLocation.label}` + for (const config of configs) { + const { locations } = config; + + const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); + + for (const privateLocation of allPrivateLocations) { + const hasLocation = monitorPrivateLocations?.some((loc) => loc.id === privateLocation.id); + const currId = this.getPolicyId(config, privateLocation, spaceId); + const hasPolicy = existingPolicies?.some((policy) => policy.id === currId); + try { + if (hasLocation) { + const newPolicy = this.generateNewPolicy( + config, + privateLocation, + savedObjectsClient, + newPolicyTemplate, + spaceId ); - } - if (hasPolicy) { - await this.updatePolicy(newPolicy, currId, savedObjectsClient); - } else { - await this.createPolicy(newPolicy, currId, savedObjectsClient); - } - } else if (hasPolicy) { - const soClient = savedObjectsClient; - const esClient = this.server.uptimeEsClient.baseESClient; - try { - await this.server.fleet.packagePolicyService.delete(soClient, esClient, [currId], { - force: true, - }); - } catch (e) { - this.server.logger.error(e); - throw new Error( - `Unable to delete Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - } with private location ${privateLocation.label}` - ); + if (!newPolicy) { + throw new Error( + `Unable to ${ + hasPolicy ? 'update' : 'create' + } Synthetics package policy for private location ${privateLocation.label}` + ); + } + + if (hasPolicy) { + policiesToUpdate.push({ ...newPolicy, id: currId }); + } else { + policiesToCreate.push({ ...newPolicy, id: currId }); + } + } else if (hasPolicy) { + policiesToDelete.push(currId); } + } catch (e) { + this.server.logger.error(e); + throw new Error( + `Unable to ${hasPolicy ? 'update' : 'create'} Synthetics package policy for monitor ${ + config[ConfigKey.NAME] + } with private location ${privateLocation.label}` + ); } - } catch (e) { - this.server.logger.error(e); - throw new Error( - `Unable to ${hasPolicy ? 'update' : 'create'} Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - } with private location ${privateLocation.label}` - ); } } + + await Promise.all([ + this.createPolicyBulk(policiesToCreate, savedObjectsClient), + this.updatePolicyBulk(policiesToUpdate, savedObjectsClient), + this.deletePolicyBulk(policiesToDelete, savedObjectsClient), + ]); + } + + async getExistingPolicies( + configs: HeartbeatConfig[], + allPrivateLocations: PrivateLocation[], + savedObjectsClient: SavedObjectsClientContract, + spaceId: string + ) { + const listOfPolicies: string[] = []; + for (const config of configs) { + for (const privateLocation of allPrivateLocations) { + const currId = this.getPolicyId(config, privateLocation, spaceId); + listOfPolicies.push(currId); + } + } + return ( + (await this.server.fleet.packagePolicyService.getByIDs(savedObjectsClient, listOfPolicies, { + ignoreMissing: true, + })) ?? [] + ); } async createPolicyBulk( @@ -248,7 +271,7 @@ export class SyntheticsPrivateLocation { ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { + if (soClient && esClient && newPolicies.length > 0) { return await this.server.fleet.packagePolicyService.bulkCreate( soClient, esClient, @@ -257,34 +280,35 @@ export class SyntheticsPrivateLocation { } } - async createPolicy( - newPolicy: NewPackagePolicy, - id: string, + async updatePolicyBulk( + updatedPolicies: Array, savedObjectsClient: SavedObjectsClientContract ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { - return await this.server.fleet.packagePolicyService.create(soClient, esClient, newPolicy, { - id, - overwrite: true, - }); + if (soClient && esClient && updatedPolicies.length > 0) { + return await this.server.fleet.packagePolicyService.bulkUpdate( + soClient, + esClient, + updatedPolicies, + { + force: true, + } + ); } } - async updatePolicy( - updatedPolicy: NewPackagePolicy, - id: string, + async deletePolicyBulk( + policyIdsToDelete: string[], savedObjectsClient: SavedObjectsClientContract ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { - return await this.server.fleet.packagePolicyService.update( + if (soClient && esClient && policyIdsToDelete.length > 0) { + return await this.server.fleet.packagePolicyService.delete( soClient, esClient, - id, - updatedPolicy, + policyIdsToDelete, { force: true, } @@ -292,15 +316,6 @@ export class SyntheticsPrivateLocation { } } - async getMonitor(id: string, savedObjectsClient: SavedObjectsClientContract) { - try { - return await this.server.fleet.packagePolicyService.get(savedObjectsClient!, id); - } catch (e) { - this.server.logger.debug(e); - return null; - } - } - async deleteMonitors( configs: HeartbeatConfig[], request: KibanaRequest, @@ -346,9 +361,7 @@ export class SyntheticsPrivateLocation { request, `Unable to delete Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` ); - await this.server.fleet.packagePolicyService.delete(soClient, esClient, policyIdsToDelete, { - force: true, - }); + await this.deletePolicyBulk(policyIdsToDelete, savedObjectsClient); } } } diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index 744003d16ea3f..aa0be87f0e818 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -12,10 +12,12 @@ import { SavedObjectsClientContract, SavedObjectsFindResult, } from '@kbn/core/server'; +import pMap from 'p-map'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { syncNewMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/add_monitor_bulk'; import { deleteMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/delete_monitor_bulk'; import { SyntheticsMonitorClient } from '../synthetics_monitor/synthetics_monitor_client'; +import { syncEditedMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/edit_monitor_bulk'; import { BrowserFields, ConfigKey, @@ -32,9 +34,8 @@ import { syntheticsMonitorType, syntheticsMonitor, } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; -import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; +import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { formatSecrets, normalizeSecrets } from '../utils/secrets'; -import { syncEditedMonitor } from '../../routes/monitor_cruds/edit_monitor'; import { validateProjectMonitor, validateMonitor, @@ -121,11 +122,13 @@ export class ProjectMonitorFormatter { public configureAllProjectMonitors = async () => { const existingMonitors = await this.getProjectMonitorsForProject(); - this.staleMonitorsMap = await this.getStaleMonitorsMap(existingMonitors); const normalizedNewMonitors: BrowserFields[] = []; - const normalizedUpdateMonitors: BrowserFields[] = []; + const normalizedUpdateMonitors: Array<{ + previousMonitor: SavedObjectsFindResult; + monitor: BrowserFields; + }> = []; for (const monitor of this.monitors) { const previousMonitor = existingMonitors.find( @@ -142,7 +145,7 @@ export class ProjectMonitorFormatter { if (this.staleMonitorsMap[monitor.id]) { this.staleMonitorsMap[monitor.id].stale = false; } - normalizedUpdateMonitors.push(normM as MonitorFields); + normalizedUpdateMonitors.push({ monitor: normM as MonitorFields, previousMonitor }); } else { normalizedNewMonitors.push(normM as MonitorFields); } @@ -151,7 +154,26 @@ export class ProjectMonitorFormatter { await this.createMonitorsBulk(normalizedNewMonitors); - await this.updateMonitorBulk(normalizedUpdateMonitors); + const { updatedCount } = await this.updateMonitors(normalizedUpdateMonitors); + + if (normalizedUpdateMonitors.length > 0) { + let updateMessage = ''; + if (updatedCount > 0) { + updateMessage = `${updatedCount} monitor${ + updatedCount > 1 ? 's' : '' + } updated successfully.`; + } + + const noChanges = normalizedUpdateMonitors.length - updatedCount; + let noChangeMessage = ''; + if (noChanges > 0) { + noChangeMessage = `${noChanges} monitor${noChanges > 1 ? 's' : ''} found with no changes.`; + } + + this.handleStreamingMessage({ + message: `${updateMessage} ${noChangeMessage}`, + }); + } await this.handleStaleMonitors(); }; @@ -274,19 +296,6 @@ export class ProjectMonitorFormatter { return hits; }; - private getExistingMonitor = async ( - journeyId: string - ): Promise> => { - const filter = `${this.projectFilter} AND ${syntheticsMonitorType}.attributes.${ConfigKey.JOURNEY_ID}: "${journeyId}"`; - const { saved_objects: savedObjects } = - await this.savedObjectsClient.find({ - type: syntheticsMonitorType, - perPage: 1, - filter, - }); - return savedObjects?.[0]; - }; - private createMonitorsBulk = async (monitors: BrowserFields[]) => { try { if (monitors.length > 0) { @@ -331,75 +340,78 @@ export class ProjectMonitorFormatter { } }; - private updateMonitorBulk = async (monitors: BrowserFields[]) => { - try { - for (const monitor of monitors) { - const previousMonitor = await this.getExistingMonitor(monitor[ConfigKey.JOURNEY_ID]!); - await this.updateMonitor(previousMonitor, monitor as MonitorFields); - } - - if (monitors.length > 0) { - this.handleStreamingMessage({ - message: `${monitors.length} monitor${ - monitors.length > 1 ? 's' : '' - } updated successfully.`, - }); - } - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - reason: 'Failed to update monitors', - details: e.message, - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to update ${monitors.length} monitors`, - }); - } + private getDecryptedMonitors = async ( + monitors: Array> + ) => { + return await pMap( + monitors, + async (monitor) => + this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( + syntheticsMonitor.name, + monitor.id, + { + namespace: monitor.namespaces?.[0], + } + ), + { concurrency: 500 } + ); }; - private updateMonitor = async ( - previousMonitor: SavedObjectsFindResult, - normalizedMonitor: MonitorFields + private updateMonitors = async ( + monitors: Array<{ + monitor: BrowserFields; + previousMonitor: SavedObjectsFindResult; + }> ): Promise<{ - editedMonitor: SavedObjectsUpdateResponse; + editedMonitors: Array>; errors: ServiceLocationErrors; + updatedCount: number; }> => { - const decryptedPreviousMonitor = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( - syntheticsMonitor.name, - previousMonitor.id, - { - namespace: previousMonitor.namespaces?.[0], - } - ); - const { - attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes }, - } = normalizeSecrets(decryptedPreviousMonitor); - const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes); - - if (hasMonitorBeenEdited) { - const monitorWithRevision = formatSecrets({ - ...normalizedPreviousMonitorAttributes, - ...normalizedMonitor, - revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, - }); - - const { editedMonitor } = await syncEditedMonitor({ - normalizedMonitor, - monitorWithRevision, - previousMonitor, - decryptedPreviousMonitor, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - savedObjectsClient: this.savedObjectsClient, - request: this.request, - spaceId: this.spaceId, - }); - return { editedMonitor, errors: [] }; + const decryptedPreviousMonitors = await this.getDecryptedMonitors( + monitors.map((m) => m.previousMonitor) + ); + + const monitorsToUpdate = []; + + for (let i = 0; i < decryptedPreviousMonitors.length; i++) { + const decryptedPreviousMonitor = decryptedPreviousMonitors[i]; + const previousMonitor = monitors[i].previousMonitor; + const normalizedMonitor = monitors[i].monitor; + + const { + attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes }, + } = normalizeSecrets(decryptedPreviousMonitor); + const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes); + + if (hasMonitorBeenEdited) { + const monitorWithRevision = formatSecrets({ + ...normalizedPreviousMonitorAttributes, + ...normalizedMonitor, + revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, + }); + monitorsToUpdate.push({ + normalizedMonitor, + previousMonitor, + monitorWithRevision, + decryptedPreviousMonitor, + }); + } } - return { errors: [], editedMonitor: decryptedPreviousMonitor }; + const { editedMonitors } = await syncEditedMonitorBulk({ + monitorsToUpdate, + server: this.server, + syntheticsMonitorClient: this.syntheticsMonitorClient, + savedObjectsClient: this.savedObjectsClient, + request: this.request, + privateLocations: this.privateLocations, + spaceId: this.spaceId, + }); + return { + editedMonitors: editedMonitors ?? [], + errors: [], + updatedCount: monitorsToUpdate.length, + }; }; private handleStaleMonitors = async () => { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts index 10aa461a50d4c..21f0e68588c73 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts @@ -121,12 +121,23 @@ describe('SyntheticsMonitorClient', () => { const id = 'test-id-1'; const client = new SyntheticsMonitorClient(syntheticsService, serverMock); - client.privateLocationAPI.editMonitor = jest.fn(); - - await client.editMonitor(monitor, id, mockRequest, savedObjectsClientMock, 'test-space'); + client.privateLocationAPI.editMonitors = jest.fn(); + + await client.editMonitors( + [ + { + monitor, + id, + }, + ], + mockRequest, + savedObjectsClientMock, + privateLocations, + 'test-space' + ); expect(syntheticsService.editConfig).toHaveBeenCalledTimes(1); - expect(client.privateLocationAPI.editMonitor).toHaveBeenCalledTimes(1); + expect(client.privateLocationAPI.editMonitors).toHaveBeenCalledTimes(1); }); it('should delete a monitor', async () => { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index 53d5f2f592043..8af7fca704ab0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; +import { KibanaRequest, SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { SyntheticsPrivateLocation } from '../private_location/synthetics_private_location'; import { SyntheticsService } from '../synthetics_service'; @@ -15,6 +15,7 @@ import { SyntheticsMonitorWithId, HeartbeatConfig, PrivateLocation, + EncryptedSyntheticsMonitor, } from '../../../common/runtime_types'; export class SyntheticsMonitorClient { @@ -76,30 +77,48 @@ export class SyntheticsMonitorClient { return { newPolicies, syncErrors }; } - async editMonitor( - editedMonitor: MonitorFields, - id: string, + async editMonitors( + monitors: Array<{ + monitor: MonitorFields; + id: string; + previousMonitor?: SavedObject; + }>, request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, + allPrivateLocations: PrivateLocation[], spaceId: string ) { - const editedConfig = formatHeartbeatRequest({ - monitor: editedMonitor, - monitorId: id, - customHeartbeatId: (editedMonitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID], - }); - - const { publicLocations } = this.parseLocations(editedConfig); + const privateConfigs: HeartbeatConfig[] = []; + const publicConfigs: HeartbeatConfig[] = []; - await this.privateLocationAPI.editMonitor(editedConfig, request, savedObjectsClient, spaceId); + for (const editedMonitor of monitors) { + const editedConfig = formatHeartbeatRequest({ + monitor: editedMonitor.monitor, + monitorId: editedMonitor.id, + customHeartbeatId: (editedMonitor.monitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID], + }); + const { publicLocations, privateLocations } = this.parseLocations(editedConfig); + if (publicLocations.length > 0) { + publicConfigs.push(editedConfig); + } - if (publicLocations.length > 0) { - return await this.syntheticsService.editConfig(editedConfig); + if (privateLocations.length > 0 || this.hasPrivateLocations(editedMonitor.previousMonitor)) { + privateConfigs.push(editedConfig); + } } - await this.syntheticsService.editConfig(editedConfig); - } + await this.privateLocationAPI.editMonitors( + privateConfigs, + request, + savedObjectsClient, + allPrivateLocations, + spaceId + ); + if (publicConfigs.length > 0) { + return await this.syntheticsService.editConfig(publicConfigs); + } + } async deleteMonitors( monitors: SyntheticsMonitorWithId[], request: KibanaRequest, @@ -119,6 +138,15 @@ export class SyntheticsMonitorClient { return pubicResponse; } + hasPrivateLocations(previousMonitor?: SavedObject) { + if (!previousMonitor) { + return false; + } + const { locations } = previousMonitor.attributes; + + return locations.some((loc) => !loc.isServiceManaged); + } + parseLocations(config: HeartbeatConfig) { const { locations } = config; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 6c815e5a2cbdf..d0211800dc6c0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -282,8 +282,10 @@ export class SyntheticsService { } } - async editConfig(monitorConfig: HeartbeatConfig) { - const monitors = this.formatConfigs([monitorConfig]); + async editConfig(monitorConfig: HeartbeatConfig | HeartbeatConfig[]) { + const monitors = this.formatConfigs( + Array.isArray(monitorConfig) ? monitorConfig : [monitorConfig] + ); this.apiKey = await this.getApiKey(); diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 23f3a1dbd18c9..0c389000f6c80 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4644,6 +4644,19 @@ "properties": { "all": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4707,6 +4720,19 @@ }, "sec": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4723,6 +4749,19 @@ }, "obs": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4739,6 +4778,19 @@ }, "main": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index f9f1252f4765e..e52effa09ab3b 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -28,6 +28,8 @@ import { FIELD_SELECTOR_TOGGLE_BUTTON, FIELD_SELECTOR_INPUT, FIELD_SELECTOR_LIST, + INSPECTOR_BUTTON, + INSPECTOR_PANEL, } from '../screens/indicators'; import { login } from '../tasks/login'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -148,11 +150,11 @@ describe('Indicators', () => { const threatFeedName = 'threat.feed.name'; cy.get(`${FIELD_SELECTOR_INPUT}`).eq(0).should('have.text', threatFeedName); - const threatIndicatorIp: string = 'threat.indicator.ip'; + const timestamp: string = '@timestamp'; cy.get(`${FIELD_SELECTOR_TOGGLE_BUTTON}`).should('exist').click(); - cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(threatIndicatorIp); + cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(timestamp); }); }); @@ -171,4 +173,22 @@ describe('Indicators', () => { }); }); }); + + describe('Request inspector', () => { + before(() => { + cy.visit(THREAT_INTELLIGENCE); + + selectRange(); + }); + + describe('when inspector button is clicked', () => { + it('should render the inspector flyout', () => { + cy.get(INSPECTOR_BUTTON).last().click({ force: true }); + + cy.get(INSPECTOR_PANEL).should('be.visible'); + + cy.get(INSPECTOR_PANEL).contains('Index patterns'); + }); + }); + }); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts index b5582da6ce8ef..2bc1b704e8159 100644 --- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts +++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts @@ -23,7 +23,7 @@ export const FLYOUT_TITLE = `[data-test-subj="tiIndicatorFlyoutTitle"]`; export const FLYOUT_TABS = `[data-test-subj="tiIndicatorFlyoutTabs"]`; -export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableMemoryTable"]`; +export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableTabRow"]`; export const FLYOUT_JSON = `[data-test-subj="tiFlyoutJsonCodeBlock"]`; @@ -45,7 +45,8 @@ export const FIELD_SELECTOR_INPUT = '[data-test-subj="comboBoxInput"]'; export const FIELD_SELECTOR_TOGGLE_BUTTON = '[data-test-subj="comboBoxToggleListButton"]'; -export const FIELD_SELECTOR_LIST = '[data-test-subj="comboBoxOptionsList"]'; +export const FIELD_SELECTOR_LIST = + '[data-test-subj="comboBoxOptionsList tiIndicatorFieldSelectorDropdown-optionsList"]'; export const FIELD_BROWSER = `[data-test-subj="show-field-browser"]`; @@ -113,3 +114,6 @@ export const INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON = export const INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON = '[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineButton"]'; +export const INSPECTOR_BUTTON = '[data-test-subj="tiIndicatorsGridInspect"]'; + +export const INSPECTOR_PANEL = '[data-test-subj="inspectorPanel"]'; diff --git a/x-pack/plugins/threat_intelligence/kibana.json b/x-pack/plugins/threat_intelligence/kibana.json index efd1e8bce761e..f55191e68a875 100644 --- a/x-pack/plugins/threat_intelligence/kibana.json +++ b/x-pack/plugins/threat_intelligence/kibana.json @@ -16,7 +16,8 @@ "kibanaUtils", "navigation", "kibanaReact", - "triggersActionsUi" + "triggersActionsUi", + "inspector" ], "requiredBundles": [ "data", diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index d3a94bbcce96e..7e046e214b547 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -16,6 +16,7 @@ import { Storage } from '@kbn/kibana-utils-plugin/public'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { KibanaContext } from '../../hooks/use_kibana'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -25,6 +26,7 @@ import { IndicatorsFiltersContext } from '../../modules/indicators/context'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; import { FieldTypesContext } from '../../containers/field_types_provider'; import { generateFieldTypeMap } from './mock_field_type_map'; +import { InspectorContext } from '../../containers/inspector'; export const localStorageMock = (): IStorage => { let store: Record = {}; @@ -125,19 +127,21 @@ export const mockedServices = { }; export const TestProvidersComponent: FC = ({ children }) => ( - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + ); export type MockedSearch = jest.Mocked; diff --git a/x-pack/plugins/threat_intelligence/public/containers/inspector/index.tsx b/x-pack/plugins/threat_intelligence/public/containers/inspector/index.tsx new file mode 100644 index 0000000000000..ed70296138688 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/inspector/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 * from './inspector'; diff --git a/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.tsx b/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.tsx new file mode 100644 index 0000000000000..622fa2df24640 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import React, { createContext, FC, useMemo } from 'react'; + +export interface InspectorContextValue { + requests: RequestAdapter; +} + +export const InspectorContext = createContext(undefined); + +export const InspectorProvider: FC = ({ children }) => { + const inspectorAdapters = useMemo(() => ({ requests: new RequestAdapter() }), []); + + return ( + {children} + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.ts b/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.ts new file mode 100644 index 0000000000000..25aa0e5ef5da9 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.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 { useCallback, useContext, useEffect, useState } from 'react'; +import { InspectorSession } from '@kbn/inspector-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from './use_kibana'; +import { InspectorContext } from '../containers/inspector'; + +const INSPECTOR_FLYOUT_TITLE = i18n.translate('xpack.threatIntelligence.inspectorFlyoutTitle', { + defaultMessage: 'Indicators search requests', +}); + +/** + * + * @returns Exposes the adapters used to analyze requests and a method to open the inspector + */ +export const useInspector = () => { + const { + services: { inspector }, + } = useKibana(); + + const inspectorAdapters = useContext(InspectorContext); + + if (!inspectorAdapters) { + throw new Error('Inspector Context is not available'); + } + + const [inspectorSession, setInspectorSession] = useState(undefined); + + const onOpenInspector = useCallback(() => { + const session = inspector.open(inspectorAdapters, { + title: INSPECTOR_FLYOUT_TITLE, + }); + setInspectorSession(session); + }, [inspectorAdapters, inspector]); + + useEffect(() => { + return () => { + if (inspectorSession) { + inspectorSession.close(); + } + }; + }, [inspectorSession]); + + return { onOpenInspector, inspectorAdapters }; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx index a85ca19dcea28..084279fe8353a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx @@ -5,21 +5,24 @@ * 2.0. */ +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; import { renderHook } from '@testing-library/react-hooks'; import { useToolbarOptions } from './use_toolbar_options'; describe('useToolbarOptions()', () => { it('should return correct value for 0 indicators total', () => { - const result = renderHook(() => - useToolbarOptions({ - browserFields: {}, - columns: [], - end: 0, - start: 0, - indicatorCount: 0, - onResetColumns: () => {}, - onToggleColumn: () => {}, - }) + const result = renderHook( + () => + useToolbarOptions({ + browserFields: {}, + columns: [], + end: 0, + start: 0, + indicatorCount: 0, + onResetColumns: () => {}, + onToggleColumn: () => {}, + }), + { wrapper: TestProvidersComponent } ); expect(result.result.current).toMatchInlineSnapshot(` @@ -45,6 +48,12 @@ describe('useToolbarOptions()', () => { , }, + "right": , }, "showDisplaySelector": false, "showFullScreenSelector": false, @@ -53,16 +62,18 @@ describe('useToolbarOptions()', () => { }); it('should return correct value for 25 indicators total', () => { - const result = renderHook(() => - useToolbarOptions({ - browserFields: {}, - columns: [], - end: 25, - start: 0, - indicatorCount: 25, - onResetColumns: () => {}, - onToggleColumn: () => {}, - }) + const result = renderHook( + () => + useToolbarOptions({ + browserFields: {}, + columns: [], + end: 25, + start: 0, + indicatorCount: 25, + onResetColumns: () => {}, + onToggleColumn: () => {}, + }), + { wrapper: TestProvidersComponent } ); expect(result.result.current).toMatchInlineSnapshot(` @@ -95,6 +106,12 @@ describe('useToolbarOptions()', () => { , }, + "right": , }, "showDisplaySelector": false, "showFullScreenSelector": false, @@ -103,16 +120,18 @@ describe('useToolbarOptions()', () => { }); it('should return correct value for 50 indicators total', () => { - const result = renderHook(() => - useToolbarOptions({ - browserFields: {}, - columns: [], - end: 50, - start: 25, - indicatorCount: 50, - onResetColumns: () => {}, - onToggleColumn: () => {}, - }) + const result = renderHook( + () => + useToolbarOptions({ + browserFields: {}, + columns: [], + end: 50, + start: 25, + indicatorCount: 50, + onResetColumns: () => {}, + onToggleColumn: () => {}, + }), + { wrapper: TestProvidersComponent } ); expect(result.result.current).toMatchInlineSnapshot(` @@ -145,6 +164,12 @@ describe('useToolbarOptions()', () => { , }, + "right": , }, "showDisplaySelector": false, "showFullScreenSelector": false, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx index a7c4148e88aef..b19d6df71463e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx @@ -7,10 +7,18 @@ import React from 'react'; import { useMemo } from 'react'; -import { EuiDataGridColumn, EuiText } from '@elastic/eui'; +import { EuiButtonIcon, EuiDataGridColumn, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { BrowserField } from '@kbn/rule-registry-plugin/common'; +import { useInspector } from '../../../../../hooks/use_inspector'; import { IndicatorsFieldBrowser } from '../../indicators_field_browser'; +const INSPECT_BUTTON_TEST_ID = 'tiIndicatorsGridInspect'; + +const INSPECT_BUTTON_TITLE = i18n.translate('xpack.threatIntelligence.inspectTitle', { + defaultMessage: 'Inspect', +}); + export const useToolbarOptions = ({ browserFields, start, @@ -27,8 +35,10 @@ export const useToolbarOptions = ({ columns: EuiDataGridColumn[]; onResetColumns: () => void; onToggleColumn: (columnId: string) => void; -}) => - useMemo( +}) => { + const { onOpenInspector: handleOpenInspector } = useInspector(); + + return useMemo( () => ({ showDisplaySelector: false, showFullScreenSelector: false, @@ -55,7 +65,25 @@ export const useToolbarOptions = ({ /> ), }, + right: ( + + ), }, }), - [start, end, indicatorCount, browserFields, columns, onResetColumns, onToggleColumn] + [ + indicatorCount, + end, + start, + browserFields, + columns, + onResetColumns, + onToggleColumn, + handleOpenInspector, + ] ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts index 82e59dc7b3ad3..02230defa9688 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { buildEsQuery, TimeRange } from '@kbn/es-query'; +import { TimeRange } from '@kbn/es-query'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { @@ -15,15 +15,16 @@ import { isErrorResponse, TimeRangeBounds, } from '@kbn/data-plugin/common'; +import { useInspector } from '../../../hooks/use_inspector'; import { useFilters } from '../../query_bar/hooks/use_filters'; import { convertAggregationToChartSeries } from '../../../common/utils/barchart'; import { RawIndicatorFieldId } from '../../../../common/types/indicator'; -import { THREAT_QUERY_BASE } from '../../../../common/constants'; import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates'; import { useKibana } from '../../../hooks/use_kibana'; import { DEFAULT_TIME_RANGE } from '../../query_bar/hooks/use_filters/utils'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from '../lib/display_name'; +import { getRuntimeMappings } from '../lib/get_runtime_mappings'; +import { getIndicatorsQuery } from '../lib/get_indicators_query'; export interface UseAggregatedIndicatorsParam { /** @@ -97,98 +98,73 @@ export const useAggregatedIndicators = ({ const { selectedPatterns } = useSourcererDataView(); + const { inspectorAdapters } = useInspector(); + const searchSubscription$ = useRef(new Subscription()); const abortController = useRef(new AbortController()); const [indicators, setIndicators] = useState([]); const [field, setField] = useState(DEFAULT_FIELD); + const { filters, filterQuery } = useFilters(); const dateRange: TimeRangeBounds = useMemo( () => queryService.timefilter.timefilter.calculateBounds(timeRange), [queryService, timeRange] ); - const { filters, filterQuery } = useFilters(); + const queryToExecute = useMemo(() => { + return getIndicatorsQuery({ timeRange, filters, filterQuery }); + }, [filterQuery, filters, timeRange]); const loadData = useCallback(async () => { const dateFrom: number = (dateRange.min as moment.Moment).toDate().getTime(); const dateTo: number = (dateRange.max as moment.Moment).toDate().getTime(); const interval = calculateBarchartColumnTimeInterval(dateFrom, dateTo); + const request = inspectorAdapters.requests.start('Indicator barchart', {}); + + request.stats({ + indexPattern: { + label: 'Index patterns', + value: selectedPatterns, + }, + }); + abortController.current = new AbortController(); - const queryToExecute = buildEsQuery( - undefined, - [ - { - query: THREAT_QUERY_BASE, - language: 'kuery', - }, - { - query: filterQuery.query as string, - language: 'kuery', - }, - ], - [ - ...filters, - { - query: { - range: { - [TIMESTAMP_FIELD]: { - gte: timeRange.from, - lte: timeRange.to, + const requestBody = { + aggregations: { + [AGGREGATION_NAME]: { + terms: { + field, + }, + aggs: { + events: { + date_histogram: { + field: TIMESTAMP_FIELD, + fixed_interval: interval, + min_doc_count: 0, + extended_bounds: { + min: dateFrom, + max: dateTo, + }, }, }, }, - meta: {}, }, - ] - ); + }, + fields: [TIMESTAMP_FIELD, field], + size: 0, + query: queryToExecute, + runtime_mappings: getRuntimeMappings(), + }; searchSubscription$.current = searchService .search>( { params: { index: selectedPatterns, - body: { - aggregations: { - [AGGREGATION_NAME]: { - terms: { - field, - }, - aggs: { - events: { - date_histogram: { - field: TIMESTAMP_FIELD, - fixed_interval: interval, - min_doc_count: 0, - extended_bounds: { - min: dateFrom, - max: dateTo, - }, - }, - }, - }, - }, - }, - fields: [TIMESTAMP_FIELD, field], // limit the response to only the fields we need - size: 0, // we don't need hits, just aggregations - query: queryToExecute, - runtime_mappings: { - 'threat.indicator.name': { - type: 'keyword', - script: { - source: threatIndicatorNamesScript(), - }, - }, - 'threat.indicator.name_origin': { - type: 'keyword', - script: { - source: threatIndicatorNamesOriginScript(), - }, - }, - }, - }, + body: requestBody, }, }, { @@ -202,27 +178,34 @@ export const useAggregatedIndicators = ({ response.rawResponse.aggregations[AGGREGATION_NAME]?.buckets; const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations); setIndicators(chartSeries); - searchSubscription$.current.unsubscribe(); + + request.stats({}).ok({ json: response }); + request.json(requestBody); } else if (isErrorResponse(response)) { + request.error({ json: response }); searchSubscription$.current.unsubscribe(); } }, - error: (msg) => { - searchService.showError(msg); + error: (requestError) => { + searchService.showError(requestError); searchSubscription$.current.unsubscribe(); + + if (requestError instanceof Error && requestError.name.includes('Abort')) { + inspectorAdapters.requests.reset(); + } else { + request.error({ json: requestError }); + } }, }); }, [ dateRange.max, dateRange.min, field, - filterQuery, - filters, + inspectorAdapters.requests, + queryToExecute, searchService, selectedPatterns, - timeRange.from, - timeRange.to, ]); const onFieldChange = useCallback( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts index 3bae1a9cecd56..e44e2e05ca230 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts @@ -13,12 +13,13 @@ import { } from '@kbn/data-plugin/common'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { Subscription } from 'rxjs'; -import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { useInspector } from '../../../hooks/use_inspector'; import { Indicator } from '../../../../common/types/indicator'; import { useKibana } from '../../../hooks/use_kibana'; -import { THREAT_QUERY_BASE } from '../../../../common/constants'; import { useSourcererDataView } from './use_sourcerer_data_view'; -import { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from '../lib/display_name'; +import { getRuntimeMappings } from '../lib/get_runtime_mappings'; +import { getIndicatorsQuery } from '../lib/get_indicators_query'; const PAGE_SIZES = [10, 25, 50]; @@ -67,6 +68,8 @@ export const useIndicators = ({ } = useKibana(); const { selectedPatterns } = useSourcererDataView(); + const { inspectorAdapters } = useInspector(); + const searchSubscription$ = useRef(); const abortController = useRef(new AbortController()); @@ -80,36 +83,9 @@ export const useIndicators = ({ pageSizeOptions: PAGE_SIZES, }); - const queryToExecute = useMemo( - () => - buildEsQuery( - undefined, - [ - { - query: THREAT_QUERY_BASE, - language: 'kuery', - }, - { - query: filterQuery.query as string, - language: 'kuery', - }, - ], - [ - ...filters, - { - query: { - range: { - ['@timestamp']: { - gte: timeRange?.from, - lte: timeRange?.to, - }, - }, - }, - meta: {}, - }, - ] - ), - [filterQuery, filters, timeRange?.from, timeRange?.to] + const query = useMemo( + () => getIndicatorsQuery({ filters, timeRange, filterQuery }), + [filterQuery, filters, timeRange] ); const loadData = useCallback( @@ -118,32 +94,30 @@ export const useIndicators = ({ setLoading(true); + const request = inspectorAdapters.requests.start('Indicator search', {}); + + request.stats({ + indexPattern: { + label: 'Index patterns', + value: selectedPatterns, + }, + }); + + const requestBody = { + query, + runtime_mappings: getRuntimeMappings(), + fields: [{ field: '*', include_unmapped: true }], + size, + from, + sort: sorting.map(({ id, direction }) => ({ [id]: direction })), + }; + searchSubscription$.current = searchService .search>( { params: { index: selectedPatterns, - body: { - size, - from, - fields: [{ field: '*', include_unmapped: true }], - query: queryToExecute, - sort: sorting.map(({ id, direction }) => ({ [id]: direction })), - runtime_mappings: { - 'threat.indicator.name': { - type: 'keyword', - script: { - source: threatIndicatorNamesScript(), - }, - }, - 'threat.indicator.name_origin': { - type: 'keyword', - script: { - source: threatIndicatorNamesOriginScript(), - }, - }, - }, - }, + body: requestBody, }, }, { @@ -158,20 +132,29 @@ export const useIndicators = ({ if (isCompleteResponse(response)) { setLoading(false); searchSubscription$.current?.unsubscribe(); + request.stats({}).ok({ json: response }); + request.json(requestBody); } else if (isErrorResponse(response)) { setLoading(false); + request.error({ json: response }); searchSubscription$.current?.unsubscribe(); } }, - error: (msg) => { - searchService.showError(msg); + error: (requestError) => { + searchService.showError(requestError); searchSubscription$.current?.unsubscribe(); + if (requestError instanceof Error && requestError.name.includes('Abort')) { + inspectorAdapters.requests.reset(); + } else { + request.error({ json: requestError }); + } + setLoading(false); }, }); }, - [queryToExecute, searchService, selectedPatterns, sorting] + [inspectorAdapters.requests, query, searchService, selectedPatterns, sorting] ); const onChangeItemsPerPage = useCallback( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index a647646861ee1..8c138ffec502b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { VFC } from 'react'; +import React, { FC, VFC } from 'react'; import { IndicatorsFilters } from './containers/indicators_filters/indicators_filters'; import { IndicatorsBarChartWrapper } from './components/indicators_barchart_wrapper/indicators_barchart_wrapper'; import { IndicatorsTable } from './components/indicators_table/indicators_table'; @@ -16,9 +16,16 @@ import { FiltersGlobal } from '../../containers/filters_global'; import QueryBar from '../query_bar/components/query_bar'; import { useSourcererDataView } from './hooks/use_sourcerer_data_view'; import { FieldTypesProvider } from '../../containers/field_types_provider'; +import { InspectorProvider } from '../../containers/inspector'; import { useColumnSettings } from './components/indicators_table/hooks/use_column_settings'; -export const IndicatorsPage: VFC = () => { +const IndicatorsPageProviders: FC = ({ children }) => ( + + {children} + +); + +const IndicatorsPageContent: VFC = () => { const { browserFields, indexPattern } = useSourcererDataView(); const columnSettings = useColumnSettings(); @@ -75,6 +82,12 @@ export const IndicatorsPage: VFC = () => { ); }; +export const IndicatorsPage: VFC = () => ( + + + +); + // Note: This is for lazy loading // eslint-disable-next-line import/no-default-export export default IndicatorsPage; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_indicators_query.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_indicators_query.ts new file mode 100644 index 0000000000000..160fa22db7632 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_indicators_query.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 { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { THREAT_QUERY_BASE } from '../../../../common/constants'; +import { RawIndicatorFieldId } from '../../../../common/types/indicator'; + +const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp; + +export const getIndicatorsQuery = ({ + filters, + filterQuery, + timeRange, +}: { + filters: Filter[]; + filterQuery: Query; + timeRange?: TimeRange; +}) => { + return buildEsQuery( + undefined, + [ + { + query: THREAT_QUERY_BASE, + language: 'kuery', + }, + { + query: filterQuery.query as string, + language: 'kuery', + }, + ], + [ + ...filters, + { + query: { + range: { + [TIMESTAMP_FIELD]: { + gte: timeRange?.from, + lte: timeRange?.to, + }, + }, + }, + meta: {}, + }, + ] + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_runtime_mappings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_runtime_mappings.ts new file mode 100644 index 0000000000000..ed62b18e7c9b2 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_runtime_mappings.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 { threatIndicatorNamesOriginScript, threatIndicatorNamesScript } from './display_name'; + +export const getRuntimeMappings = () => + ({ + 'threat.indicator.name': { + type: 'keyword', + script: { + source: threatIndicatorNamesScript(), + }, + }, + 'threat.indicator.name_origin': { + type: 'keyword', + script: { + source: threatIndicatorNamesOriginScript(), + }, + }, + } as const); diff --git a/x-pack/plugins/threat_intelligence/public/types.ts b/x-pack/plugins/threat_intelligence/public/types.ts index 976033934aa9a..f803231e97587 100644 --- a/x-pack/plugins/threat_intelligence/public/types.ts +++ b/x-pack/plugins/threat_intelligence/public/types.ts @@ -20,6 +20,7 @@ import { DataViewBase } from '@kbn/es-query'; import { BrowserField } from '@kbn/rule-registry-plugin/common'; import { Store } from 'redux'; import { DataProvider } from '@kbn/timelines-plugin/common'; +import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; export interface SecuritySolutionDataViewBase extends DataViewBase { fields: Array; @@ -45,6 +46,7 @@ export type Services = { triggersActionsUi: TriggersActionsStart; timelines: TimelinesUIStart; securityLayout: any; + inspector: InspectorPluginStart; } & CoreStart; export interface LicenseAware { diff --git a/x-pack/plugins/transform/kibana.json b/x-pack/plugins/transform/kibana.json index 6045d50ea26b9..dba104b3dcd2c 100644 --- a/x-pack/plugins/transform/kibana.json +++ b/x-pack/plugins/transform/kibana.json @@ -13,7 +13,8 @@ "savedObjects", "share", "triggersActionsUi", - "fieldFormats" + "fieldFormats", + "unifiedSearch" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index 3a3781070a863..91fb215c814b0 100644 --- a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -14,12 +14,13 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { savedObjectsPluginMock } from '@kbn/saved-objects-plugin/public/mocks'; import { SharePluginStart } from '@kbn/share-plugin/public'; -import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { AppDependencies } from '../app_dependencies'; import { MlSharedContext } from './shared_context'; import type { GetMlSharedImportsReturnType } from '../../shared_imports'; -import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); @@ -46,6 +47,7 @@ const appDependencies: AppDependencies = { share: { urlGenerators: { getUrlGenerator: jest.fn() } } as unknown as SharePluginStart, ml: {} as GetMlSharedImportsReturnType, triggersActionsUi: {} as jest.Mocked, + unifiedSearch: {} as jest.Mocked, }; export const useAppDependencies = () => { diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index 9be316b5f1d16..4c3d2ededd2c3 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -22,11 +22,13 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { ScopedHistory } from '@kbn/core/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { GetMlSharedImportsReturnType } from '../shared_imports'; export interface AppDependencies { @@ -48,6 +50,8 @@ export interface AppDependencies { ml: GetMlSharedImportsReturnType; spaces?: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection?: UsageCollectionStart; } export const useAppDependencies = () => { diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index 851ebf374e3e7..d5b4f07c6ff4d 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -29,7 +29,7 @@ export async function mountManagementSection( const startServices = await getStartServices(); const [core, plugins] = startServices; const { application, chrome, docLinks, i18n, overlays, theme, savedObjects, uiSettings } = core; - const { data, share, spaces, triggersActionsUi } = plugins; + const { data, share, spaces, triggersActionsUi, unifiedSearch } = plugins; const { docTitle } = chrome; // Initialize services @@ -57,6 +57,7 @@ export async function mountManagementSection( spaces, ml: await getMlSharedImports(), triggersActionsUi, + unifiedSearch, }; const unmountAppCallback = renderApp(element, appDependencies); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx index 762f3bb72863f..dfbfced03b949 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -13,9 +13,11 @@ import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { PLUGIN } from '../../../../../../common/constants'; import { SearchItems } from '../../../../hooks/use_search_items'; import { StepDefineFormHook, QUERY_LANGUAGE_KUERY } from '../step_define'; +import { useAppDependencies } from '../../../../app_dependencies'; interface SourceSearchBarProps { dataView: SearchItems['dataView']; @@ -27,6 +29,17 @@ export const SourceSearchBar: FC = ({ dataView, searchBar state: { errorMessage, searchInput }, } = searchBar; + const { + uiSettings, + notifications, + http, + docLinks, + data, + storage, + unifiedSearch, + usageCollection, + } = useAppDependencies(); + return ( = ({ dataView, searchBar disableAutoFocus={true} dataTestSubj="transformQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" + appName={PLUGIN.getI18nName()} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> } isOpen={errorMessage?.query === searchInput.query && errorMessage?.message !== ''} diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 762dfd2bcaab8..25f8845644f45 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -17,11 +17,13 @@ import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { registerFeature } from './register_feature'; import { getTransformHealthRuleType } from './alerting'; export interface PluginsDependencies { data: DataPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; dataViews: DataViewsPublicPluginStart; management: ManagementSetup; home: HomePublicPluginSetup; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx index c149a6c4143cd..0b7db1ebeceba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx @@ -326,7 +326,7 @@ export const RuleQuickEditButtons: React.FunctionComponent = ({ > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 02f2a722c9012..3bde062935a86 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -1876,7 +1876,8 @@ describe('rules_list with disabled items', () => { }); }); -describe('Rules list bulk actions', () => { +// Failing: https://github.com/elastic/kibana/issues/141052 +describe.skip('Rules list bulk actions', () => { let wrapper: ReactWrapper; async function setup(authorized: boolean = true) { diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index 8c0191187bc4b..d4d4c48c60336 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -27,86 +27,88 @@ export default function ({ getService }: FtrProviderContext) { const mapUsage = apiResponse.stack_stats.kibana.plugins.maps; delete mapUsage.timeCaptured; - expect(mapUsage.geoShapeAggLayersCount).eql(1); - expect(mapUsage.indexPatternsWithGeoFieldCount).eql(6); - expect(mapUsage.indexPatternsWithGeoPointFieldCount).eql(4); - expect(mapUsage.indexPatternsWithGeoShapeFieldCount).eql(2); - expect(mapUsage.mapsTotalCount).eql(26); - expect(mapUsage.basemaps).eql({}); - expect(mapUsage.joins).eql({ term: { min: 1, max: 1, total: 3, avg: 0.11538461538461539 } }); - expect(mapUsage.layerTypes).eql({ - es_docs: { min: 1, max: 2, total: 18, avg: 0.6923076923076923 }, - es_agg_grids: { min: 1, max: 1, total: 6, avg: 0.23076923076923078 }, - es_point_to_point: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - es_top_hits: { min: 1, max: 1, total: 2, avg: 0.07692307692307693 }, - es_agg_heatmap: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - kbn_tms_raster: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - ems_basemap: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - ems_region: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - }); - expect(mapUsage.resolutions).eql({ - coarse: { min: 1, max: 1, total: 4, avg: 0.15384615384615385 }, - super_fine: { min: 1, max: 1, total: 3, avg: 0.11538461538461539 }, - }); - expect(mapUsage.scalingOptions).eql({ - limit: { min: 1, max: 2, total: 14, avg: 0.5384615384615384 }, - clusters: { min: 1, max: 1, total: 1, avg: 0.038461538461538464 }, - mvt: { min: 1, max: 1, total: 3, avg: 0.11538461538461539 }, - }); - expect(mapUsage.attributesPerMap).eql({ - customIconsCount: { - avg: 0, - max: 0, - min: 0, + expect(mapUsage).eql({ + geoShapeAggLayersCount: 1, + indexPatternsWithGeoFieldCount: 6, + indexPatternsWithGeoPointFieldCount: 4, + indexPatternsWithGeoShapeFieldCount: 2, + mapsTotalCount: 27, + basemaps: {}, + joins: { term: { min: 1, max: 1, total: 3, avg: 0.1111111111111111 } }, + layerTypes: { + es_docs: { min: 1, max: 2, total: 19, avg: 0.7037037037037037 }, + es_agg_grids: { min: 1, max: 1, total: 6, avg: 0.2222222222222222 }, + es_point_to_point: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + es_top_hits: { min: 1, max: 1, total: 2, avg: 0.07407407407407407 }, + es_agg_heatmap: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + kbn_tms_raster: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + ems_basemap: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + ems_region: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, }, - dataSourcesCount: { - avg: 1.1538461538461537, - max: 5, - min: 1, + resolutions: { + coarse: { min: 1, max: 1, total: 4, avg: 0.14814814814814814 }, + super_fine: { min: 1, max: 1, total: 3, avg: 0.1111111111111111 }, }, - emsVectorLayersCount: { - idThatDoesNotExitForEMSFileSource: { - avg: 0.038461538461538464, - max: 1, - min: 1, - }, + scalingOptions: { + limit: { min: 1, max: 2, total: 14, avg: 0.5185185185185185 }, + clusters: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + mvt: { min: 1, max: 1, total: 4, avg: 0.14814814814814814 }, }, - layerTypesCount: { - BLENDED_VECTOR: { - avg: 0.038461538461538464, - max: 1, - min: 1, + attributesPerMap: { + customIconsCount: { + avg: 0, + max: 0, + min: 0, }, - EMS_VECTOR_TILE: { - avg: 0.038461538461538464, - max: 1, + dataSourcesCount: { + avg: 1.1481481481481481, + max: 5, min: 1, }, - GEOJSON_VECTOR: { - avg: 0.8076923076923077, - max: 4, - min: 1, + emsVectorLayersCount: { + idThatDoesNotExitForEMSFileSource: { + avg: 0.037037037037037035, + max: 1, + min: 1, + }, }, - HEATMAP: { - avg: 0.038461538461538464, - max: 1, - min: 1, + layerTypesCount: { + BLENDED_VECTOR: { + avg: 0.037037037037037035, + max: 1, + min: 1, + }, + EMS_VECTOR_TILE: { + avg: 0.037037037037037035, + max: 1, + min: 1, + }, + GEOJSON_VECTOR: { + avg: 0.7777777777777778, + max: 4, + min: 1, + }, + HEATMAP: { + avg: 0.037037037037037035, + max: 1, + min: 1, + }, + MVT_VECTOR: { + avg: 0.25925925925925924, + max: 1, + min: 1, + }, + RASTER_TILE: { + avg: 0.037037037037037035, + max: 1, + min: 1, + }, }, - MVT_VECTOR: { - avg: 0.23076923076923078, - max: 1, + layersCount: { + avg: 1.1851851851851851, + max: 6, min: 1, }, - RASTER_TILE: { - avg: 0.038461538461538464, - max: 1, - min: 1, - }, - }, - layersCount: { - avg: 1.1923076923076923, - max: 6, - min: 1, }, }); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index 3c5442d6fdc90..1110bbb875c73 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -394,7 +394,7 @@ export default function ({ getService }: FtrProviderContext) { ); expect(messages).to.have.length(2); - expect(messages[0]).eql('1 monitor updated successfully.'); + expect(messages[0]).eql(' 1 monitor found with no changes.'); expect(messages[1].createdMonitors).eql([]); expect(messages[1].failedMonitors).eql([]); expect(messages[1].updatedMonitors).eql( @@ -458,10 +458,10 @@ export default function ({ getService }: FtrProviderContext) { })), }; - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT) - .set('kbn-xsrf', 'true') - .send(editedMonitors); + const messages = await parseStreamApiResponse( + projectMonitorEndpoint, + JSON.stringify(editedMonitors) + ); const updatedMonitorsResponse = await Promise.all( projectMonitors.monitors.map((monitor) => { @@ -476,6 +476,7 @@ export default function ({ getService }: FtrProviderContext) { updatedMonitorsResponse.forEach((response) => { expect(response.body.monitors[0].attributes.revision).eql(2); }); + expect(messages[0]).eql('1 monitor updated successfully. '); } finally { await Promise.all([ projectMonitors.monitors.map((monitor) => { @@ -504,7 +505,7 @@ export default function ({ getService }: FtrProviderContext) { ); expect(messages).to.have.length(2); - expect(messages[0]).eql('1 monitor updated successfully.'); + expect(messages[0]).eql(' 1 monitor found with no changes.'); expect(messages[1].createdMonitors).eql([]); expect(messages[1].failedMonitors).eql([]); expect(messages[1].deletedMonitors).eql([]); @@ -566,6 +567,7 @@ export default function ({ getService }: FtrProviderContext) { const { monitors } = getResponse.body; expect(monitors[0]).eql(undefined); + expect(messages[0]).eql(` 1 monitor found with no changes.`); expect(messages[1]).eql(`Monitor ${secondMonitor.id} deleted successfully`); expect(messages[2].createdMonitors).eql([]); expect(messages[2].failedMonitors).eql([]); @@ -922,7 +924,7 @@ export default function ({ getService }: FtrProviderContext) { JSON.stringify(projectMonitors) ); expect(messages).to.have.length(2); - expect(messages[0]).eql('1 monitor updated successfully.'); + expect(messages[0]).eql('1 monitor updated successfully. '); expect(messages[1].updatedMonitors).eql([projectMonitors.monitors[0].id]); // ensure that monitor can still be decrypted diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/all_types.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/all_types.ts index 323fe9041e1b6..354ba79a46a56 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/all_types.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/all_types.ts @@ -12,6 +12,7 @@ import { deleteAllAlerts, deleteSignalsIndex, getSecurityTelemetryStats, + removeTimeFieldsFromTelemetryStats, } from '../../../../utils'; import { deleteAllExceptions } from '../../../../../lists_api_integration/utils'; @@ -41,14 +42,43 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); - it('should have initialized empty/zero values when no rules are running', async () => { + it('should only have task metric values when no rules are running', async () => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); expect(stats).to.eql({ - detection_rules: [], - security_lists: [], - endpoints: [], - diagnostics: [], + detection_rules: [ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ], + security_lists: [ + [ + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, + ], + ], + endpoints: [ + [ + { + name: 'Security Solution Telemetry Endpoint Metrics and Info task', + passed: true, + }, + ], + ], + diagnostics: [ + [ + { + name: 'Security Solution Telemetry Diagnostics task', + passed: true, + }, + ], + ], }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts index 627faebb2daaa..eb5f5c9a923bb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts @@ -21,6 +21,7 @@ import { getSecurityTelemetryStats, createExceptionList, createExceptionListItem, + removeTimeFieldsFromTelemetryStats, } from '../../../../utils'; import { deleteAllExceptions } from '../../../../../lists_api_integration/utils'; @@ -100,7 +101,15 @@ export default ({ getService }: FtrProviderContext) => { // Get the stats and ensure they're empty await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).to.eql([]); + removeTimeFieldsFromTelemetryStats(stats); + expect(stats.detection_rules).to.eql([ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ]); }); }); @@ -148,7 +157,15 @@ export default ({ getService }: FtrProviderContext) => { // Get the stats and ensure they're empty await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).to.eql([]); + removeTimeFieldsFromTelemetryStats(stats); + expect(stats.detection_rules).to.eql([ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ]); }); }); @@ -196,7 +213,15 @@ export default ({ getService }: FtrProviderContext) => { // Get the stats and ensure they're empty await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).to.eql([]); + removeTimeFieldsFromTelemetryStats(stats); + expect(stats.detection_rules).to.eql([ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ]); }); }); @@ -244,7 +269,15 @@ export default ({ getService }: FtrProviderContext) => { // Get the stats and ensure they're empty await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).to.eql([]); + removeTimeFieldsFromTelemetryStats(stats); + expect(stats.detection_rules).to.eql([ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ]); }); }); @@ -292,7 +325,15 @@ export default ({ getService }: FtrProviderContext) => { // Get the stats and ensure they're empty await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).to.eql([]); + removeTimeFieldsFromTelemetryStats(stats); + expect(stats.detection_rules).to.eql([ + [ + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, + ], + ]); }); }); }); @@ -350,7 +391,7 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - expect(stats.detection_rules).length(1); + expect(stats.detection_rules).length(2); const detectionRule = stats.detection_rules[0][0]; expect(detectionRule['@timestamp']).to.be.a('string'); expect(detectionRule.cluster_uuid).to.be.a('string'); @@ -408,9 +449,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule); + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)); expect(detectionRules).to.eql([ { @@ -428,6 +470,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[0].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); @@ -479,9 +525,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule); + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)); expect(detectionRules).to.eql([ { @@ -499,6 +546,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[0].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); @@ -550,9 +601,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule); + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)); expect(detectionRules).to.eql([ { @@ -570,6 +622,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[0].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); @@ -621,9 +677,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule); + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)); expect(detectionRules).to.eql([ { @@ -641,6 +698,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[0].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); @@ -692,9 +753,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule); + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)); expect(detectionRules).to.eql([ { @@ -712,6 +774,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[0].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); @@ -787,11 +853,12 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const detectionRules = stats.detection_rules .flat() - .map((obj: { detection_rule: any }) => obj.detection_rule) + .map((obj: any) => (obj.passed != null ? obj : obj.detection_rule)) .sort((obj1: { entries: { name: number } }, obj2: { entries: { name: number } }) => { - return obj1.entries.name - obj2.entries.name; + return obj1?.entries?.name - obj2?.entries?.name; }); expect(detectionRules).to.eql([ @@ -825,6 +892,10 @@ export default ({ getService }: FtrProviderContext) => { os_types: [], rule_version: detectionRules[1].rule_version, }, + { + name: 'Security Solution Detection Rule Lists Telemetry', + passed: true, + }, ]); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts index c56936f016b58..4db09b123d3db 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts @@ -19,6 +19,7 @@ import { getSecurityTelemetryStats, createExceptionListItem, createExceptionList, + removeTimeFieldsFromTelemetryStats, } from '../../../../utils'; import { deleteAllExceptions } from '../../../../../lists_api_integration/utils'; @@ -72,10 +73,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - + removeTimeFieldsFromTelemetryStats(stats); const trustedApplication = stats.security_lists .flat() - .map((obj: { trusted_application: any }) => obj.trusted_application); + .map((obj: any) => (obj.passed != null ? obj : obj.trusted_application)); expect(trustedApplication).to.eql([ { created_at: trustedApplication[0].created_at, @@ -95,6 +96,10 @@ export default ({ getService }: FtrProviderContext) => { policies: [], }, }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); @@ -138,12 +143,12 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); - + removeTimeFieldsFromTelemetryStats(stats); const trustedApplication = stats.security_lists .flat() - .map((obj: { trusted_application: any }) => obj.trusted_application) + .map((obj: any) => (obj.passed != null ? obj : obj.trusted_application)) .sort((obj1: { entries: { name: number } }, obj2: { entries: { name: number } }) => { - return obj1.entries.name - obj2.entries.name; + return obj1?.entries?.name - obj2?.entries?.name; }); expect(trustedApplication).to.eql([ @@ -183,6 +188,10 @@ export default ({ getService }: FtrProviderContext) => { policies: [], }, }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); @@ -210,9 +219,10 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const securityLists = stats.security_lists .flat() - .map((obj: { endpoint_exception: any }) => obj.endpoint_exception); + .map((obj: any) => (obj.passed != null ? obj : obj.endpoint_exception)); expect(securityLists).to.eql([ { created_at: securityLists[0].created_at, @@ -228,6 +238,10 @@ export default ({ getService }: FtrProviderContext) => { name: ENDPOINT_LIST_ID, os_types: [], }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); @@ -271,11 +285,12 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const securityLists = stats.security_lists .flat() - .map((obj: { endpoint_exception: any }) => obj.endpoint_exception) + .map((obj: any) => (obj.passed != null ? obj : obj.endpoint_exception)) .sort((obj1: { entries: { name: number } }, obj2: { entries: { name: number } }) => { - return obj1.entries.name - obj2.entries.name; + return obj1?.entries?.name - obj2?.entries?.name; }); expect(securityLists).to.eql([ @@ -307,6 +322,10 @@ export default ({ getService }: FtrProviderContext) => { name: ENDPOINT_LIST_ID, os_types: [], }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); @@ -346,9 +365,11 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const endPointEventFilter = stats.security_lists .flat() - .map((obj: { endpoint_event_filter: any }) => obj.endpoint_event_filter); + .map((obj: any) => (obj.passed != null ? obj : obj.endpoint_event_filter)); + expect(endPointEventFilter).to.eql([ { created_at: endPointEventFilter[0].created_at, @@ -364,6 +385,10 @@ export default ({ getService }: FtrProviderContext) => { name: ENDPOINT_EVENT_FILTERS_LIST_ID, os_types: ['linux'], }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); @@ -407,11 +432,12 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getSecurityTelemetryStats(supertest, log); + removeTimeFieldsFromTelemetryStats(stats); const endPointEventFilter = stats.security_lists .flat() - .map((obj: { endpoint_event_filter: any }) => obj.endpoint_event_filter) + .map((obj: any) => (obj.passed != null ? obj : obj.endpoint_event_filter)) .sort((obj1: { entries: { name: number } }, obj2: { entries: { name: number } }) => { - return obj1.entries.name - obj2.entries.name; + return obj1?.entries?.name - obj2?.entries?.name; }); expect(endPointEventFilter).to.eql([ @@ -443,6 +469,10 @@ export default ({ getService }: FtrProviderContext) => { name: ENDPOINT_EVENT_FILTERS_LIST_ID, os_types: ['macos'], }, + { + name: 'Security Solution Lists Telemetry', + passed: true, + }, ]); }); }); diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 31d13a52f72da..093be64c26d8a 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -80,6 +80,7 @@ export * from './get_web_hook_action'; export * from './index_event_log_execution_events'; export * from './install_prepackaged_rules'; export * from './refresh_index'; +export * from './remove_time_fields_from_telemetry_stats'; export * from './remove_server_generated_properties'; export * from './remove_server_generated_properties_including_rule_id'; export * from './resolve_simple_rule_output'; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_time_fields_from_telemetry_stats.ts b/x-pack/test/detection_engine_api_integration/utils/remove_time_fields_from_telemetry_stats.ts new file mode 100644 index 0000000000000..7c0931b4b5ae9 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/remove_time_fields_from_telemetry_stats.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 { unset } from 'lodash'; + +export const removeTimeFieldsFromTelemetryStats = (stats: any) => { + Object.entries(stats).forEach(([, value]: [unknown, any]) => { + value.forEach((entry: any, i: number) => { + entry.forEach((e: any, j: number) => { + unset(value, `[${i}][${j}].time_executed_in_ms`); + unset(value, `[${i}][${j}].start_time`); + unset(value, `[${i}][${j}].end_time`); + }); + }); + }); +}; diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts index a030a988656b9..2a63e1c8b3905 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts @@ -144,5 +144,18 @@ export default function (providerContext: FtrProviderContext) { expect(packageInfo.name).to.equal('apache'); await uninstallPackage(testPkgName, testPkgVersion); }); + describe('Pkg verification', () => { + it('should return validation error for unverified input only pkg', async function () { + const res = await supertest.get(`/api/fleet/epm/packages/input_only/0.1.0`).expect(400); + const error = res.body; + + expect(error?.attributes?.type).to.equal('verification_failed'); + }); + it('should not return validation error for unverified input only pkg if ignoreUnverified is true', async function () { + await supertest + .get(`/api/fleet/epm/packages/input_only/0.1.0?ignoreUnverified=true`) + .expect(200); + }); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/agent/input/input.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/agent/input/input.yml.hbs new file mode 100644 index 0000000000000..1ba86fa98a2f8 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/agent/input/input.yml.hbs @@ -0,0 +1,18 @@ +paths: +{{#each paths}} + - {{this}} +{{/each}} + +{{#if tags}} +tags: +{{#each tags as |tag i|}} + - {{tag}} +{{/each}} +{{/if}} + +{{#if pipeline}} +pipeline: {{pipeline}} +{{/if}} + +data_stream: + dataset: {{data_stream.dataset}} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/changelog.yml b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/changelog.yml new file mode 100644 index 0000000000000..d122ea7a8e6ba --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.1.0" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/package-spec/pull/325 diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/docs/README.md new file mode 100644 index 0000000000000..9f29c89e0f5ef --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/docs/README.md @@ -0,0 +1 @@ +# Custom Logs \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/fields/input.yml b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/fields/input.yml new file mode 100644 index 0000000000000..f5851c64b6b3a --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/fields/input.yml @@ -0,0 +1,4 @@ +- name: input.name + type: constant_keyword + description: Sample field to be added. + value: logs \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-logo.svg b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-logo.svg new file mode 100644 index 0000000000000..6268dd88f3b3d --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-screenshot.png b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-screenshot.png new file mode 100644 index 0000000000000..d7a56a3ecc078 Binary files /dev/null and b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/img/sample-screenshot.png differ diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/manifest.yml new file mode 100644 index 0000000000000..30533c4911fed --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/src/input_only-0.1.0/manifest.yml @@ -0,0 +1,45 @@ +format_version: 1.0.0 +name: input_only +title: Custom Logs +description: >- + Read lines from active log files with Elastic Agent. +type: input +version: 0.1.0 +license: basic +categories: + - custom +policy_templates: + - name: first_policy_template + type: logs + title: Custom log file + description: Collect your custom log files. + input: logfile + template_path: input.yml.hbs + vars: + - name: paths + type: text + title: Paths + multi: true + required: true + show_user: true + - name: tags + type: text + title: Tags + multi: true + required: true + show_user: false + - name: ignore_older + type: text + title: Ignore events older than + required: false + default: 72h +icons: + - src: "/img/sample-logo.svg" + type: "image/svg+xml" +screenshots: + - src: "/img/sample-screenshot.png" + title: "Sample screenshot" + size: "600x600" + type: "image/png" +owner: + github: elastic/integrations \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip new file mode 100644 index 0000000000000..de9bdbb141980 Binary files /dev/null and b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip differ diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip.sig b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip.sig new file mode 100644 index 0000000000000..194bdc4e9b1c4 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/package_verification/packages/zips/input_only_unverified-0.1.0.zip.sig @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- +Version: verified-1.0.0 +Comment: Signed with elastic-package (using GopenPGP: https://gopenpgp.org) + +wsDzBAABCgAnBQJi2nUGCZDSoYKnsOAMFBYhBOpp3B9hL6vyZ4UHQdKhgqew4AwU +AADJswv/X0nLu+yKYY8baxwLE9Y5xk5dYpXMFgOtXWxzvZ6im4Bkwy1BPiVHPS7Y +iJQKGPdy7g2UkJ4fdJXhpEIi0FDHH8i8QsOLQ8f2hfITRzw4Qi7evTQ23Y2+KXab +GmBERuvz5V6K5hux5MehMOEEwiHJw4tnn6JWDi39Z+C+FDnDILtl3cukZ5Qe9dYi +H82+eOU1Ub0bHMciD2V0Z0sjR3I3DpbZni7cdcpILxAI162VbrOuylJbJUlSIziU +UQCkH15W5cyM8nQmp2h4VeA7MIlEA+aaEpCLXfPl9vLqTqPPZxIvtT/+iMkit+Qc +NgjaAIeOElE7osl973bbd5l0JLn/5nbXVs7PvbTrS1j8nu+cqZesqpxTbpOmU8xP +2opzEYnIA+qjAp2sxPObgnRiUT6Tu1yDX8BfaE5D+BbyEQ4k+aqm+AR0joc2mdoF +RYVBt7yyAXB5kjlv0xkxqkKFqg04lgYHYN5Mt0E0mjzxw0D9/GEFSpjqqOYvIIxZ +RS9dWbzJ +=V/oa +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts index 1040498a9bd05..3b1f9c2065180 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts @@ -6,6 +6,8 @@ */ import expect from '@kbn/expect'; +import { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '@kbn/controls-plugin/common'; + import { FtrProviderContext } from '../../../../ftr_provider_context'; const DRILLDOWN_TO_PIE_CHART_NAME = 'Go to pie chart dashboard'; @@ -18,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardDrilldownsManage = getService('dashboardDrilldownsManage'); const PageObjects = getPageObjects([ 'dashboard', + 'dashboardControls', 'common', 'header', 'timePicker', @@ -46,20 +49,202 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await security.testUser.restoreDefaults(); - await clearFilters(dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME); - await clearFilters(dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME); }); - const clearFilters = async (dashboardName: string) => { - await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); - await filterBar.removeAllFilters(); - await PageObjects.dashboard.clearUnsavedChanges(); - }; + describe('test dashboard to dashboard drilldown', async () => { + before(async () => { + await createDrilldown(); + }); + + after(async () => { + await cleanFiltersAndTimePicker(dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME); + await cleanFiltersAndTimePicker(dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME); + }); + + it('use dashboard to dashboard drilldown via onClick action', async () => { + await testCircularDashboardDrilldowns( + dashboardDrilldownPanelActions.clickActionByText.bind(dashboardDrilldownPanelActions) // preserve 'this' + ); + }); + + it('use dashboard to dashboard drilldown via getHref action', async () => { + await testCircularDashboardDrilldowns( + dashboardDrilldownPanelActions.openHrefByText.bind(dashboardDrilldownPanelActions) // preserve 'this' + ); + }); + + it('delete dashboard to dashboard drilldown', async () => { + // delete drilldown + await PageObjects.dashboard.switchToEditMode(); + await dashboardPanelActions.openContextMenu(); + await dashboardDrilldownPanelActions.expectExistsManageDrilldownsAction(); + await dashboardDrilldownPanelActions.clickManageDrilldowns(); + await dashboardDrilldownsManage.expectsManageDrilldownsFlyoutOpen(); + + await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]); + await dashboardDrilldownsManage.closeFlyout(); + + // check that drilldown notification badge is not shown + expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0); + }); + + it('browser back/forward navigation works after drilldown navigation', async () => { + await PageObjects.dashboard.loadSavedDashboard( + dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME + ); + const originalTimeRangeDurationHours = + await PageObjects.timePicker.getTimeDurationInHours(); + await brushAreaChart(); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await navigateWithinDashboard(async () => { + await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME); + }); + // check that new time range duration was applied + const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); + + await navigateWithinDashboard(async () => { + await browser.goBack(); + }); + + expect(await PageObjects.timePicker.getTimeDurationInHours()).to.be( + originalTimeRangeDurationHours + ); + }); + + const testCircularDashboardDrilldowns = async ( + drilldownAction: (text: string) => Promise + ) => { + await testPieChartDashboardDrilldown(drilldownAction); + expect(await filterBar.getFilterCount()).to.be(1); + + const originalTimeRangeDurationHours = + await PageObjects.timePicker.getTimeDurationInHours(); + await PageObjects.dashboard.clearUnsavedChanges(); + + // brush area chart and drilldown back to pie chat dashboard + await brushAreaChart(); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await navigateWithinDashboard(async () => { + await drilldownAction(DRILLDOWN_TO_PIE_CHART_NAME); + }); + + // because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied) + expect(await filterBar.getFilterCount()).to.be(1); + await pieChart.expectPieSliceCount(1); + // check that new time range duration was applied + const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); + await PageObjects.dashboard.clearUnsavedChanges(); + }; + + const cleanFiltersAndTimePicker = async (dashboardName: string) => { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await filterBar.removeAllFilters(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.dashboard.clearUnsavedChanges(); + }; + }); - it('create dashboard to dashboard drilldown', async () => { + describe('test dashboard to dashboard drilldown with controls', async () => { + before('add controls and make selections', async () => { + /** Source Dashboard */ + await createDrilldown(); + await addControls(dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME, [ + { field: 'geo.src', type: OPTIONS_LIST_CONTROL }, + { field: 'bytes', type: RANGE_SLIDER_CONTROL }, + ]); + const controlIds = await PageObjects.dashboardControls.getAllControlIds(); + const [optionsListControl, rangeSliderControl] = controlIds; + await PageObjects.dashboardControls.optionsListOpenPopover(optionsListControl); + await PageObjects.dashboardControls.optionsListPopoverSelectOption('CN'); + await PageObjects.dashboardControls.optionsListPopoverSelectOption('US'); + await PageObjects.dashboardControls.rangeSliderWaitForLoading(); // wait for range slider to respond to options list selections before proceeding + await PageObjects.dashboardControls.rangeSliderSetLowerBound(rangeSliderControl, '1000'); + await PageObjects.dashboardControls.rangeSliderSetUpperBound(rangeSliderControl, '15000'); + await PageObjects.dashboard.clickQuickSave(); + await PageObjects.dashboard.waitForRenderComplete(); + + /** Destination Dashboard */ + await addControls(dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME, [ + { field: 'geo.src', type: OPTIONS_LIST_CONTROL }, + ]); + }); + + after(async () => { + await cleanFiltersAndControls(dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME); + await cleanFiltersAndControls(dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME); + }); + + it('use dashboard to dashboard drilldown via onClick action', async () => { + await testSingleDashboardDrilldown( + dashboardDrilldownPanelActions.clickActionByText.bind(dashboardDrilldownPanelActions) // preserve 'this' + ); + }); + + it('use dashboard to dashboard drilldown via getHref action', async () => { + await testSingleDashboardDrilldown( + dashboardDrilldownPanelActions.openHrefByText.bind(dashboardDrilldownPanelActions) // preserve 'this' + ); + }); + + const addControls = async ( + dashboardName: string, + controls: Array<{ field: string; type: string }> + ) => { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await PageObjects.common.clearAllToasts(); // toasts get in the way of bottom "Save and close" button in create control flyout + + for (const control of controls) { + await PageObjects.dashboardControls.createControl({ + controlType: control.type, + dataViewTitle: 'logstash-*', + fieldName: control.field, + }); + } + await PageObjects.dashboard.clickQuickSave(); + }; + + const testSingleDashboardDrilldown = async ( + drilldownAction: (text: string) => Promise + ) => { + await testPieChartDashboardDrilldown(drilldownAction); + + // drilldown creates filter pills for control selections + expect(await filterBar.hasFilter('geo.src', 'CN, US')).to.be(true); + expect(await filterBar.hasFilter('bytes', '1,000 to 15,000')).to.be(true); + + // control filter pills impact destination dashboard controls + const controlIds = await PageObjects.dashboardControls.getAllControlIds(); + const optionsListControl = controlIds[0]; + await PageObjects.dashboardControls.optionsListOpenPopover(optionsListControl); + expect( + await PageObjects.dashboardControls.optionsListPopoverGetAvailableOptionsCount() + ).to.equal(2); + await PageObjects.dashboardControls.optionsListEnsurePopoverIsClosed(optionsListControl); + + // can clear unsaved changes badge after drilldown with controls + await PageObjects.dashboard.clearUnsavedChanges(); + + // clean up filters in destination dashboard + await filterBar.removeAllFilters(); + expect(await filterBar.getFilterCount()).to.be(0); + await PageObjects.dashboard.clickQuickSave(); + }; + + const cleanFiltersAndControls = async (dashboardName: string) => { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await filterBar.removeAllFilters(); + await PageObjects.dashboardControls.deleteAllControls(); + await PageObjects.dashboard.clickQuickSave(); + }; + }); + + const createDrilldown = async () => { await PageObjects.dashboard.gotoDashboardEditMode( dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME ); + await PageObjects.common.clearAllToasts(); // toasts get in the way of bottom "Create drilldown" button in flyout // create drilldown await dashboardPanelActions.openContextMenu(); await dashboardDrilldownPanelActions.expectExistsCreateDrilldownAction(); @@ -87,63 +272,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } ); await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); - }); - - it('use dashboard to dashboard drilldown via onClick action', async () => { - await testDashboardDrilldown( - dashboardDrilldownPanelActions.clickActionByText.bind(dashboardDrilldownPanelActions) // preserve 'this' - ); - }); - - it('use dashboard to dashboard drilldown via getHref action', async () => { - await testDashboardDrilldown( - dashboardDrilldownPanelActions.openHrefByText.bind(dashboardDrilldownPanelActions) // preserve 'this' - ); - }); - - it('delete dashboard to dashboard drilldown', async () => { - // delete drilldown - await PageObjects.dashboard.switchToEditMode(); - await dashboardPanelActions.openContextMenu(); - await dashboardDrilldownPanelActions.expectExistsManageDrilldownsAction(); - await dashboardDrilldownPanelActions.clickManageDrilldowns(); - await dashboardDrilldownsManage.expectsManageDrilldownsFlyoutOpen(); - - await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]); - await dashboardDrilldownsManage.closeFlyout(); - - // check that drilldown notification badge is not shown - expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0); - }); - - it('browser back/forward navigation works after drilldown navigation', async () => { - await PageObjects.dashboard.loadSavedDashboard( - dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME - ); - const originalTimeRangeDurationHours = - await PageObjects.timePicker.getTimeDurationInHours(); - await brushAreaChart(); - await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); - await navigateWithinDashboard(async () => { - await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME); - }); - // check that new time range duration was applied - const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); - - await navigateWithinDashboard(async () => { - await browser.goBack(); - }); + }; - expect(await PageObjects.timePicker.getTimeDurationInHours()).to.be( - originalTimeRangeDurationHours + const testPieChartDashboardDrilldown = async ( + drilldownAction: (text: string) => Promise + ) => { + await PageObjects.dashboard.gotoDashboardEditMode( + dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME ); - }); - const testDashboardDrilldown = async (drilldownAction: (text: string) => Promise) => { // trigger drilldown action by clicking on a pie and picking drilldown action by it's name - await pieChart.clickOnPieSlice('40000'); - await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await retry.waitFor('drilldown action menu to appear', async () => { + // avoid flakiness of context menu opening + await pieChart.clickOnPieSlice('40000'); // + return await testSubjects.exists('multipleActionsContextMenu'); + }); const href = await dashboardDrilldownPanelActions.getActionHrefByText( DRILLDOWN_TO_AREA_CHART_NAME @@ -160,25 +303,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // check that we drilled-down with filter from pie chart - expect(await filterBar.getFilterCount()).to.be(1); - const originalTimeRangeDurationHours = - await PageObjects.timePicker.getTimeDurationInHours(); - await PageObjects.dashboard.clearUnsavedChanges(); - - // brush area chart and drilldown back to pie chat dashboard - await brushAreaChart(); - await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); - await navigateWithinDashboard(async () => { - await drilldownAction(DRILLDOWN_TO_PIE_CHART_NAME); - }); - - // because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied) - expect(await filterBar.getFilterCount()).to.be(1); - await pieChart.expectPieSliceCount(1); - // check that new time range duration was applied - const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); - await PageObjects.dashboard.clearUnsavedChanges(); + expect(await filterBar.hasFilter('memory', '40,000 to 80,000')).to.be(true); }; }); diff --git a/x-pack/test/functional/apps/maps/group2/adhoc_data_view.ts b/x-pack/test/functional/apps/maps/group2/adhoc_data_view.ts new file mode 100644 index 0000000000000..15824a1beb63f --- /dev/null +++ b/x-pack/test/functional/apps/maps/group2/adhoc_data_view.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const security = getService('security'); + const PageObjects = getPageObjects(['maps']); + + describe('maps adhoc data view', () => { + before(async () => { + await security.testUser.setRoles(['global_maps_all', 'test_logstash_reader'], { + skipBrowserRefresh: true, + }); + await PageObjects.maps.loadSavedMap('adhoc data view'); + }); + + it('should render saved map with adhoc data view', async () => { + const tooltipText = await PageObjects.maps.getLayerTocTooltipMsg('adhocDataView'); + expect(tooltipText).to.equal( + 'adhocDataView\nFound 908 documents.\nResults narrowed by global search\nResults narrowed by global time' + ); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + }); +} diff --git a/x-pack/test/functional/apps/maps/group2/index.js b/x-pack/test/functional/apps/maps/group2/index.js index b8b3a15a10ed5..884c7a500c446 100644 --- a/x-pack/test/functional/apps/maps/group2/index.js +++ b/x-pack/test/functional/apps/maps/group2/index.js @@ -59,6 +59,7 @@ export default function ({ loadTestFile, getService }) { }); loadTestFile(require.resolve('./es_geo_grid_source')); + loadTestFile(require.resolve('./adhoc_data_view')); loadTestFile(require.resolve('./embeddable')); }); } diff --git a/x-pack/test/functional/fixtures/kbn_archiver/maps.json b/x-pack/test/functional/fixtures/kbn_archiver/maps.json index fcd89cc41c724..92507529a1b8a 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/maps.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/maps.json @@ -990,6 +990,28 @@ "version": "WzQ5LDJd" } +{ + "id": "68f85360-3913-11ed-aa60-654006132508", + "type": "map", + "namespaces": [ + "default" + ], + "updated_at": "2022-09-20T18:39:40.421Z", + "version": "WzM0OSwxXQ==", + "attributes": { + "title": "adhoc data view", + "description": "", + "layerListJSON": "[{\"id\":\"e5b830e4-d939-4b82-b3df-ccb4c3fce478\",\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"id\":\"5883a356-bef3-4e87-89f4-6dfd95a5794d\",\"indexPatternId\":\"1a9589c7-c919-4d35-bd4c-fbe1bcf8dfe4\",\"label\":\"logstash-*\",\"scalingType\":\"MVT\",\"tooltipProperties\":[],\"type\":\"ES_SEARCH\"},\"type\":\"MVT_VECTOR\",\"visible\":true,\"style\":{},\"label\":\"adhocDataView\"}]", + "mapStateJSON": "{\"adHocDataViews\":[{\"id\":\"1a9589c7-c919-4d35-bd4c-fbe1bcf8dfe4\",\"title\":\"logstash-*\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"fieldFormats\":{},\"runtimeFieldMap\":{},\"fieldAttrs\":{},\"allowNoIndex\":false,\"name\":\"logstash adhoc data view\"}],\"zoom\":2.36,\"center\":{\"lon\":-116.75537,\"lat\":55.05932},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-22T00:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[{\"meta\":{\"index\":\"1a9589c7-c919-4d35-bd4c-fbe1bcf8dfe4\",\"params\":{\"lt\":1000,\"gte\":0},\"field\":\"bytes\",\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"range\",\"key\":\"bytes\",\"value\":{\"lt\":1000,\"gte\":0}},\"query\":{\"range\":{\"bytes\":{\"lt\":1000,\"gte\":0}}},\"$state\":{\"store\":\"appState\"}}],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "references": [], + "migrationVersion": { + "map": "8.4.0" + }, + "coreMigrationVersion": "8.5.0" +} + { "attributes": { "description": "", diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts index 3b30f1b64dc2b..3d333d91da153 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts @@ -20,6 +20,7 @@ export default ({ getService }: FtrProviderContext) => { const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; + const INDEX_ALIAS = '*'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; @@ -53,12 +54,12 @@ export default ({ getService }: FtrProviderContext) => { describe('Users:', () => { it(`${obsOnlySpacesAll.username} should be able to access the APM alert in ${SPACE1}`, async () => { const indexNames = await getAPMIndexName(obsOnlySpacesAll, SPACE1); - expect(indexNames.includes(`${APM_ALERT_INDEX}-${SPACE1}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below + expect(indexNames.includes(`${APM_ALERT_INDEX}-${INDEX_ALIAS}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below }); it(`${superUser.username} should be able to access the APM alert in ${SPACE1}`, async () => { const indexNames = await getAPMIndexName(superUser, SPACE1); - expect(indexNames.includes(`${APM_ALERT_INDEX}-${SPACE1}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below + expect(indexNames.includes(`${APM_ALERT_INDEX}-${INDEX_ALIAS}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below }); it(`${secOnlyRead.username} should NOT be able to access the APM alert in ${SPACE1}`, async () => { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index a5b13c083b278..730486ccf94f5 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -96,7 +96,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('when there is data,', () => { + // Version specific: https://github.com/elastic/kibana/issues/141298 + describe.skip('when there is data,', () => { before(async () => { indexedData = await endpointTestResources.loadEndpointData({ numHosts: 3 }); await pageObjects.endpoint.navigateToEndpointList(); diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index f74434f72227e..58a434bd0ca91 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -6,13 +6,13 @@ */ import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; -import { EsArchiver } from '@kbn/es-archiver'; import { SavedObject } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; import { getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { getTestDataLoader } from '../lib/test_data_loader'; type TestResponse = Record; @@ -51,11 +51,11 @@ const getDestinationSpace = (originSpaceId?: string) => { return DEFAULT_SPACE_ID; }; -export function resolveCopyToSpaceConflictsSuite( - esArchiver: EsArchiver, - supertestWithAuth: SuperTest, - supertestWithoutAuth: SuperTest -) { +export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { + const testDataLoader = getTestDataLoader(context); + const supertestWithAuth = context.getService('supertest'); + const supertestWithoutAuth = context.getService('supertestWithoutAuth'); + const getVisualizationAtSpace = async (spaceId: string): Promise> => { return supertestWithAuth .get(`${getUrlPrefix(spaceId)}/api/saved_objects/visualization/cts_vis_3_${spaceId}`) @@ -487,16 +487,8 @@ export function resolveCopyToSpaceConflictsSuite( }); describe('single-namespace types', () => { - beforeEach(() => - esArchiver.load( - 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); - afterEach(() => - esArchiver.unload( - 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); + beforeEach(async () => await testDataLoader.beforeEach()); + afterEach(async () => await testDataLoader.afterEach()); const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` }; const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` }; @@ -638,16 +630,8 @@ export function resolveCopyToSpaceConflictsSuite( const includeReferences = false; const createNewCopies = false; describe(`multi-namespace types with "overwrite" retry`, () => { - before(() => - esArchiver.load( - 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); - after(() => - esArchiver.unload( - 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); + before(async () => await testDataLoader.beforeEach()); + after(async () => await testDataLoader.afterEach()); const testCases = tests.multiNamespaceTestCases(); testCases.forEach(({ testTitle, objects, retries, statusCode, response }) => { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts index 1b39cd5d77302..2f1788ae348f9 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts @@ -11,11 +11,7 @@ import { resolveCopyToSpaceConflictsSuite } from '../../common/suites/resolve_co import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export -export default function resolveCopyToSpaceConflictsTestSuite({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const supertestWithAuth = getService('supertest'); - const esArchiver = getService('esArchiver'); - +export default function resolveCopyToSpaceConflictsTestSuite(context: FtrProviderContext) { const { resolveCopyToSpaceConflictsTest, createExpectNonOverriddenResponseWithReferences, @@ -27,7 +23,7 @@ export default function resolveCopyToSpaceConflictsTestSuite({ getService }: Ftr createExpectUnauthorizedAtSpaceWithoutReferencesResult, createMultiNamespaceTestCases, NON_EXISTENT_SPACE_ID, - } = resolveCopyToSpaceConflictsSuite(esArchiver, supertestWithAuth, supertestWithoutAuth); + } = resolveCopyToSpaceConflictsSuite(context); describe('resolve copy to spaces conflicts', () => { [ diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts index 454c49f9a6ca6..2248c67cd8219 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; import { resolveCopyToSpaceConflictsSuite } from '../../common/suites/resolve_copy_to_space_conflicts'; // eslint-disable-next-line import/no-default-export -export default function resolveCopyToSpaceConflictsTestSuite({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const supertestWithAuth = getService('supertest'); - const esArchiver = getService('esArchiver'); - +export default function resolveCopyToSpaceConflictsTestSuite(context: FtrProviderContext) { const { resolveCopyToSpaceConflictsTest, createExpectNonOverriddenResponseWithReferences, @@ -23,7 +19,7 @@ export default function resolveCopyToSpaceConflictsTestSuite({ getService }: Ftr createMultiNamespaceTestCases, NON_EXISTENT_SPACE_ID, originSpaces, - } = resolveCopyToSpaceConflictsSuite(esArchiver, supertestWithAuth, supertestWithoutAuth); + } = resolveCopyToSpaceConflictsSuite(context); describe('resolve copy to spaces conflicts', () => { originSpaces.forEach((spaceId) => { diff --git a/yarn.lock b/yarn.lock index 4e681bb73808f..1d81724e537b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1530,10 +1530,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@64.0.4": - version "64.0.4" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-64.0.4.tgz#4a6a997a3f43f459c82e3b992ac2e6ab318d5a12" - integrity sha512-4wpZcVJyNvxfZA58kVSwnJxIbWCrFriU6vARr/DoDB6Vvt/5zHeFrHzXFjdo+hqTWZApPoEQlK7aJ7FDZTEbkw== +"@elastic/eui@64.0.5": + version "64.0.5" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-64.0.5.tgz#d68dcebc2acd9ec360a84cb8688919de0d802417" + integrity sha512-6sJpnHYIUErA+IFJBLrXB8a86E+VR6dOBKpWVfQZVL+ZXrt4fy6uWXNompsz0geT8ylvW85oSpfpxRh+cgb0lw== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160"